Find That Function

Marvin explains two utilities he's written in Microsoft C 5.0 to build a function finder for the MS-DOS that takes some of the grunt work out of revising your C source code.


August 01, 1988
URL:http://www.drdobbs.com/windows/find-that-function/184407981

AUG88: FIND THAT FUNCTION

Marvin Hymowech works as a programmer for Condor Computer Corp. Previously he taught mathematics at the University of Michigan in Ann Arbor. He may be reached at 4906 Cole Blvd, Ypsilanti, MI 48197.


You're a C programmer who has just changed employers. You are suddenly responsible for maintaining more than 100 source code files and perhaps three times as many C functions. In wandering through your new wealth of source code, you find a reference to a function called get_input which sounds like a good thing to look at next. But which file is it in? Or perhaps you recall seeing a function called save_screen (or was it save_scrn?) that would be just the thing to use in that new function you have to write, but you just can't recall where you saw it. What to do?

The traditional way of handling these problems is this: First, you use a text-searching program such as grep (Unix) or ts (from The Norton Utilities) and search through all your source files for every reference to the function you are looking for until you finally locate the reference that is the function definition. Then, you bring up the source file in your editor. Next, you search for the function again until you finally find the function definition. By this time, the reason you were looking for it has usually slipped your mind.

Having suffered through this situation several times over the years (and not just with other peoples' code!), I resolved to find a better way. This article presents my solution: a function finder for MS-DOS. Using the function finder (actually, it consists of two programs) speeds up the process considerably. You simply invoke the finder with the function name as an argument. The program then scans an index file, invokes your editor using the appropriate source file, and, for sophisticated editors such as Brief, even positions the cursor at the function definition.

The two programs that make up the function finder, bldfuncs and getf are written in C. Although I used Microsoft C (Version 5.0) to compile the programs presented here, the code could be ported to other compilers (or to Unix) with minimal changes. The editor I use is Brief, Version 2.01, but any other editor could be used.

How It Works

As I mentioned earlier, my solution consists of two programs, bldfuncs and getf. bldfuncs constructs an index by reading through all the C source files specified on the command line and then constructing a text file (funcs.txt) that contains the name of each C source file read and a list of all functions defined therein. The resulting file looks something like this:

file_1.c:
       function_1a
       function_1b
       ...
       function_1z;

file_2.c:
       function_2a
       ...

Once the index file has been built by bldfuncs getf can be run with a function name as a command-line argument. getf reads funcs.txt until it finds a match for the specified function name (wildcard characters are allowed) and then gets the name of the C source file in which the function resides. Next, getf constructs a DOS command line invoking the editor of your choice with this source file as the file to be edited. Then, a DOS exec call is performed to replace getf by the editor in memory. For editors such as Brief, which let you specify editor commands on the command line as well, even more is possible: You can specify an appropriate search command to position the cursor at an occurrence of the function name, which will hopefully be the actual definition of the sought for function.

In brief then, you run bldfuncs once to construct funcs.txt and thereafter whenever major changes occur in the distribution of functions among your source files. If you then need that elusive function save_screen (save_scrn?), you just type getf save_scr*. One of three things will happen then:

    1. getf will politely tell you that there is no such function in funcs.txt.

    2. getf will find that there is exactly one function in funcs.txt matching the mask save_scr*, and the next thing you will see is the desired source file in your editor, with the cursor positioned at the exact place where the function is defined (maybe).

    3. getf will find more than one match for save_scr and will present you with a menu of such functions and the source files in which they are defined; after you choose one of these files, it will be presented in the editor, as in case 2.

bldfuncs is intended to read C files that work with almost any C compiler on the market; it is assumed, however, that the files will compile without errors.

Dealing with Editors

Now for some of the details. In an effort to land exactly at the function definition after the editor was invoked, I first tried instructing Brief to start at the beginning of the file and search forward for the function name. Most programmers, however, code so that functions are more likely to be referenced (in the source file in which they are defined) before they are defined rather than after. In keeping with this general rule, I wrote a custom Brief macro to start at the end of the file and search backward for the function name instead. This worked much better and in fact lands me at the exact spot about 50 percent of the time; when it doesn't work, I just use the key assigned to search_again to continue looking.

The command line getf uses to invoke the editor is built by using the DOS environment variable GETFEDIT, which allows users to customize getf to their editor of choice. On my machine, my autoexec.bat file contains the line:

     set GETFEDlT= b-m'funcsrch %%s'%%s

Where funcsrch is the Brief macro mentioned earlier. The -m option tells Brief to invoke the macro in quotes after it begins. getf replaces the first %%s by the function name it is looking for and the second %%s by the file name in which it will be found.

My Brief macro funcsrch is shown in Example 1, this page. As you can see, Brief is programmed in a language that is roughly a cross between C and Lisp. The effect of this macro is first to position the cursor at the end of the file being edited and then to search backward for a specified string (the function name in this instance). I found this approach was more effective for positioning the cursor exactly on the definition of the sought-for function rather than on a reference to it. The built-in macro end_of_buffer positions the cursor at the end of the buffer and then the search_back macro is used to look for the string s obtained from the command line invoking the macro. The built-in external variable _s_pat is then set to the same value as s so that subsequent invocations of the macro search_again (assigned to Shift-F5 on my machine) will continue to search backward for the function name in question (also note that the external variable dir is set to 0 so that subsequent search_again 's will go backward, not forward).

Building the Function List

bldfuncs (see Listing One) presented the thorniest problem: The program had to be sufficiently cognizant of C syntax to extract the names of functions defined in a source file yet ignore the similar constructions, such as prototype function declarations, used for argument type checking.

The approach I decided upon was to use several "filter functions" in succession to simplify the character stream obtained from the source file until I was able to extract the function names. Explaining from the bottom up (which is essentially the order in which the program was coded), the lowest-level filter is filter_cmt, which reads the raw character stream and returns a character stream identical to its input except that all comments have been eliminated. (Each filter function behaves like fgetc does--each takes a FILE pointer as input and returns characters, the value EOF, or other special values marking structures that have been "collapsed.") filter_cmt is essentially a small finite-state machine, the states reflecting whether you have just received an (asterisk), or a / (slash), or neither. Some C compilers allow nested comments, so filter_cmt maintains a cmt_level variable, which is incremented when a /* (slash, asterisk) is received and decremented when an */ (asterisk, slash) is received; characters are returned only when cmt_level reaches zero.

Example 1: A search macro for the Brief editor

(macro funcsrch
     (
          (string s)
          (extern _s_pat _dir center_line)
          (get_parm 0 s)
          (message "locating function %s..." s)
          (end_of_buffer)
          (if ( > (search_back s) 0 )
               (
                    (message "function %s found" s)
                    (center_line)
                    (sprintf _s_pat "%s" s)
                    ( = _dir 0)
               )
          ;else
               (
                    (top_of_buffer)
                    (beep)
                    (message "function %s not found" s)
               )
          )
     )
)

Example 2: Make file for bldfuncs

bldfuncs.obj: bldfuncs.c
              cl /c bldfuncs.c

bldfuncs.exe: bldfuncs.obj
             link bldfuncs+\msc5\lib\setargv/ST:14000/NOE;

The next filter is filter_quotes, which reads the character stream returned by filter_cmt and replaces any quoted string (delimited by either single or double quotes) by the special value QUOTES, which is used as a place marker for the original string. Higher-level filters look for matching curly braces, for example, and would be confused by curly braces occurring within quotes. The only subtlety in filter_quotes is to treat escaped characters (preceded by a backslash) correctly to avoid terminating a quoted string prematurely--for example, the string \i (slash, curly brace).

Next in the hierarchy comes filter_ppdir, whose task is to read from the stream provided by filter_quotes and eliminate all preprocessor directives. (I have made the simplifying assumption that no function will be defined either via a #define directive or in an include file. Although such peculiarities are possible, they are rare and are not constructs found when good programming style is employed.) The gotcha to be avoided in this filter is that #define constructs may extend to several programming lines, so filter_ppdir is careful to scan for escaped new-line sequences (a backslash followed immediately by a newline character) before deciding that a #define construct has terminated.

Next, filter_curly_braces reads the stream returned by filter_ppdir and replaces all characters between matching curly brace pairs (( )) by the special value BRACES. This is accomplished by maintaining a brace count that begins at zero and is incremented when a left brace is encountered and decremented when a right brace is encountered. While the count is nonzero, no incoming character is returned to the caller.

Getting Down to Functions

At this point, the filtered input stream consists of external data items, function models for type checking, and actual function definitions. To simplify the task further, filter_parens reads the character stream provided by filter_curly_braces and eliminates all characters between parentheses, returning instead the special value PARENS. This approach eliminates thorny problems in the declaration of formal function parameters--for example, in the function definition:

int function1(a,b,c)

int (*a)( );
char(*b)( );
int c;
{
}

Some data initialization expressions can have constructions that resemble functions also--for example:

     int size = sizeof(array)/sizeof(element);

To avoid such problems, filter_data reads the stream provided by filter_parens and deletes the right-hand side of any assignment and the equal sign, leaving just the semicolon.

The stream returned by filter_data is sufficiently simple that it can now be used to extract the names of defined functions. The routine get_names_one_file opens the file specified by the parameter source_file_name; reads this file via filter data; and writes the resulting function names to the file specified by the parameter fp_out, which is the already opened FILE pointer to the output stream for funcs.txt. This is done by storing characters from filter_data until EOF, or a semicolon, or the PARENS symbol is encountered. You are really only interested in sequences like this that end with PARENS because a sequence ending with a semicolon before a PARENS symbol cannot represent a function definition.

The routine get_fn_name is now used to extract the function name from the stored line, by scanning backward from the PARENS symbol until a character is encountered that is neither an underscore nor an alphanumeric. The decision as to whether this is a function definition or a type-checking construction is made as follows: If the first non-white-space character encountered after the PARENS symbol is a semicolon or a comma, it is a type-checking construction. The comma might occur as follows:

     int func1(int, char), func2(int);

If you have an actual function definition, you bypass all characters until the BRACES symbol is encountered because certainly no function definition can occur between the PARENS and BRACES symbols. It only remains to append the function name to the output file, funcs.txt.

Finishing Off blsfuncs

The main function of bldfuncs opens funcs.txt and calls get_names_one_file for every source file specified on the input line. To enable expansion of wildcards on the command line, the module setargv (provided with the Microsoft C compiler) is linked in with bldfuncs. (See the make file in bldfuncs in Example 2, page 31.) This module lets you use a command such as:

     bldfuncs *.c \othersource\*.c

which results in a funcs.txt file containing the names of functions in every C source file in both the current directory and the directory \othersource.

bldfuncs uses lots of stack space for local storage, so my make file specifies a stack size of 14000 in the link step:

     link bldfuncs + \msc5\lib\setargv/ST:14000/NOE;

Finally, Listing Two is the file bldfuncs.doc, which contains a help page for bldfuncs.

The Second Half

Now let's look at getf (Listing Three), whosejob is to scan funcs.txt for a specified function name (or for all namesmatching a specified pattern) and to present the appropriate source file in the editor. As the first step in the process, getf looks for the DOS environment variable GETFEDIT, which must contain the control string used to construct the command line that invokes the editor. For example, if your editor were named edit, you would first issue the DOS command:

set GETFEDIT = edit % s

and if this line were placed in your autoexec.bat file, it would look like:

set GETFEDIT=edit %%s

Note the use of the %% sequence to avoid interpretation of the % symbol by command.com.

The Microsoft string function strstr is used to verify that there is at least one occurrence of %s in GETFEDIT. (Given two strings s1 and s2, strstr(s1,s2) returns a character pointer to the first occurrence of s2 in s1 or NULL if there is none.) Then, the string function strtok is used to scan GETFEDIT for an initial token (delimited by white space) that should be the program name of the editor. Recall that given two strings s and delim, strtok(s,delim) scans s for a token ending with a character from the delim string, replaces this character with a null, and returns a character pointer to this token in s. Subsequent calls to strtok have the form strtok(NULL,delim) and return character pointers to subsequent tokens, finally returning NULL when no more tokens are to be had.

The program name of the editor is stored in pgm_name, and the remainder of the GETFEDIT string is stored in arg1_ctl; these are used later in constructing an exec call to invoke the editor.

Next, getf opens funcs.txt and scans for file names, which end with a colon. Any such name is saved in file_token. Having obtained file_token, getf scans for function names that match the pattern func_name (obtained from the getf command line) using the function patn_match. Any such match is stored in the arrays func_choices and (corresponding) file_choices indexed by the integer num_choices.

When this scan terminates, num_choices is examined to see if any matches were found. If there was no match, an appropriate message is printed and the editor is not invoked. If there was exactly one match, the function edit is invoked to bring up the specified file and function in the editor. If several matches occurred, the function ask_for_file is invoked to prompt the user for a choice among these, and then the function edit is invoked as in the case of one match.

The function patn_match() accepts a pattern and a string as arguments and returns TRUE or FALSE depending upon whether or not a match occurred. The pattern-matching rules are tailored to the case of function identifiers as follows: a ? (question mark) matches any single character, an * (asterisk) matches the remainder of any string, and a % (percent sign) matches any string up to the next underscore character or the end of the string.

The function edit() accepts a function and a file as parameters and uses the previously obtained strings pgm_name and arg1_ctl (parsed from the GETFEDIT variable) as parameters for an execlp call, which, if successful, will overlay the currently executing program, getf, with the editor. The p in execlp serves as a reminder that the DOS PATH variable is used to locate pgm_name.

The function ask_for_file uses the arrays func_choices and file_choices and the index variable num_choices to present the user with a menu of possible functions matching the pattern entered on the getf command line and the corresponding files in which they reside. If the command getf get_%_data were issued, a typical menu might look like:

Which one? (CR to exit)
1. get_all_data in getdata.c
2. get_good_data in valid.c
3. get_some_data in input.c

Enter number:

When a listed number is chosen, the edit function is invoked using the specified elements of func_choices and file_choices.

A make file for getf is shown in Example 3, page 33. In order to avoid any stack-space problems, a generous allocation of 14,000 stack bytes is made in the link step:

     link getf/ST:14000;

The stack allocation of 14,000 bytes is the same for both bldfuncs and getf.

Like bldfuncs, getf also has a short help file (see Listing Four ).

Summing Up

I have found these two programs in conjunction to be an excellent timesaver whenever I have to go wandering through source code files and have been using them extensively since I wrote them. The programs have saved me a great deal of time, and fall into the performance category of "fast enough." Although I've no doubt that there are more efficient ways of parsing through C source code than my multiple-filter strategy, the method I chose had two outstanding advantages over other methods: it was easy to program, and it was simple to debug.

Of course, the techniques presented in this article could be generalized to languages other than C. getf would require almost no changes, but a new version of bldfuncs would be required in order to do the parsing specific to the language desired. I'd be interested in hearing from anyone who does the conversion to another language.

_FIND THAT FUNCTION_ by Marvin Hymowech

[LISTING ONE]



/*
 * bldfuncs.c: Construct a table of the functions defined in source files.
 * Copyright (c) 1988 Marvin Hymowech
 *
 * Usage:   bldfuncs file1.c file2.c ...
 *          (wildcards are allowed also, e.g. *.c )
 *          The output table is named funcs.txt.
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#define LINT_ARGS

#define TRUE      1
#define FALSE     0
#define OK        0
#define ERROR     1

/* return values for filter functions below.
 * EOF or any character is also possible.
 */
#define BRACES    -2
#define PARENS    -3
#define QUOTES    -4

/* function declarations for type checking */
char *get_fn_name(char *);
int get_names_one_file(char *, FILE *);

main(argc, argv)
int argc;
char **argv;
{
   FILE *fp_out;
   char *current_file;
   int num_files, i;

   if( argc < 2 )
   {
      fprintf( stderr, "wrong number of parameters" );
      exit(1);
   }

   if( (fp_out = fopen("funcs.txt", "w")) == NULL )
   {
      fprintf( stderr, "can't open %s\n", "funcs.txt" );
      exit(1);
   }

   /* build a function list for each file on the command line,
    * and write the list to the file funcs.txt.
    */
   printf( "Creating funcs.txt...\n" );
   num_files = argc - 1;
   for ( i = 1; i <= num_files; i++ )
   {                                /* tell the user where we're at */
      printf( "%30s: %3d of %3d files\n",
                           current_file = strlwr(*++argv), i, num_files );
      if( get_names_one_file( current_file, fp_out ) != OK )
      {     /* use strlwr to lower-case the name - cosmetic only */
         fprintf( stderr, "can't process %s", current_file );
         exit(1);
      }
   }

   fclose(fp_out);
   exit(0);
}

/* open the .c file source_file_name, scan it for function definitions,
 * and write a listing to fp_out in the form:
 *          source_file_name:
 *             function-1
 *             function-2 ...
 *             function-n;
 * Return values: OK or ERROR
 */
int
get_names_one_file(source_file_name, fp_out)
char *source_file_name;
FILE *fp_out;
{
   int line_len, c, got_fn_defn = FALSE;
   #define LINE_LEN 8192
   char line[LINE_LEN], *name_ptr, fn_name[64];
   FILE *fp_source;
                                          /* open the input source file */
   if( (fp_source = fopen(source_file_name, "r")) == NULL )
      return ERROR;
                                          /* write "source file name:" */
   sprintf( line, "\n%s:", source_file_name );
   fprintf( fp_out, line );

   while( TRUE )
   {
      line_len = 0;           /* using the filter_data() char stream */
                              /* collect chars until a semicolon or PARENS */
      while( (c = filter_data(fp_source)) != EOF && c != ';' && c != PARENS )
         line[line_len++] = c;
      if( c == EOF )
         break;
      if( c == ';' )          /* Bypass externals representing data items. */
         continue;            /* Now we know line ended with PARENS. */
      line[ line_len ] = 0;   /* Terminate line. */

         /* At this point, line either contains a function definition
          * or a function type declaration or something else, perhaps
          * an occurrence of parenthese in a data item definition.
          * We only want function definitions.
          */
                        /* Extract the function name from this possible */
                        /* function definition, and save it in fn_name. */
      strcpy( fn_name, get_fn_name(line) );
                        /* Exclude the case of parenthetical expressions    */
                        /* in a data defintion, e.g. within array brackets. */
      if( !fn_name[0] )
         continue;
                                       /* skip white space */
      while( (c = filter_data(fp_source)) != EOF && isspace(c) )
         ;
      if( c == ';' || c == ',' )       /* functions type check declaration */
         continue;                     /* so bypass it */
      if( c == EOF )
         break;
                                       /* skip any parameter declarations - */
      while( c != BRACES && c != EOF )    /* eat until braces or EOF */
         c = filter_data(fp_source);

                     /* append this function definition to the output table */
      fprintf( fp_out, "\n\t%s", fn_name );
      got_fn_defn = TRUE;
   }
   fclose(fp_source);
                              /* got_fn_defn FALSE if no functions in file */
                               /* write file terminator */
   fputs( got_fn_defn ? ";\n" : "\n\t;\n", fp_out );
   return OK;
}

/* assuming that the input line ends with a function name,
 * extract and return this name (as a pointer into the input line).
 */
char *
get_fn_name(line)
char *line;
{
   char *name_ptr;
   int len;

   if( !(len = strlen(line)) )
      return line;

   name_ptr = line + len - 1;

   while( isspace(*name_ptr) )      /* skip trailing white space */
      name_ptr--;
   *(name_ptr + 1) = 0;             /* terminate fn name */

                                 /* function names consist entirely of */
                                 /* alphanumeric characters and underscores */
   while( (isalnum(*name_ptr) || *name_ptr == '_') && name_ptr >= line )
      name_ptr--;
                                 /* if this alleged function name begins */
   if( isdigit(*++name_ptr) )    /* with a digit, return an empty string */
      return( name_ptr + strlen(name_ptr) );
   return name_ptr;              /* else return the function name */
}

/* using the stream returned by filter_parens() as input,
 * return a character stream in which any data initialization
 * expressions between an equals sign and a semicolon
 * have been replaced by a single semicolon.
 * This will filter out anything involving parentheses in a
 * data initialization expression, which might otherwise be
 * mistaken for a function defintion.
 */
int filter_data(fp_source)
FILE *fp_source;
{
   int c;

   if( (c = filter_parens(fp_source)) != '=' )
      return c;
   while( (c = filter_parens(fp_source)) != ';' && c != EOF )
      ;
   return c;
}

/* using the stream returned by filter_curly_braces() as input,
 * return a character stream in which all characters
 * between '(' and the matching ')' have been replaced
 * by the single special value PARENS (any nested parentheses within
 * this top level pair will also have been eaten).
 * This will filter out anything within the parentheses delimiting
 * the arguments in a function definition.
 */
int
filter_parens(fp_source)
FILE *fp_source;
{
   int paren_cnt, c;

   if( (c = filter_curly_braces(fp_source)) != '(' )
      return c;
   paren_cnt = 1;
   while( paren_cnt )
      switch( filter_curly_braces(fp_source) )
      {
         case ')':
            paren_cnt--;
            break;
         case '(':
            paren_cnt++;
            break;
         case EOF:
            return EOF;
      }
   return PARENS;
}

/* using the stream returned by filter_ppdir() as input,
 * return a character stream in which all characters
 * between '{' and the matching '}' have been replaced
 * by the single special value BRACES (any nested braces within
 * this top level pair will also have been eaten).
 * This will filter out anything internal to a function.
 */
int
filter_curly_braces(fp_source)
FILE *fp_source;
{
   int brace_cnt, c;

   if( (c = filter_ppdir(fp_source)) != '{' )
      return c;
   brace_cnt = 1;
   while( brace_cnt )      /* wait for brace count to return to zero */
      switch( filter_ppdir(fp_source) )
      {
         case '}':
            brace_cnt--;   /* subtract right braces */
            break;
         case '{':
            brace_cnt++;   /* add left braces */
            break;
         case EOF:
            return EOF;
      }
   return BRACES;          /* brace count is now zero */
}

#define MAXLINE 1024

/* using the stream returned by filter_quotes() as input,
 * return a character stream in which all preprocessor
 * directives have been eaten.
 */
int
filter_ppdir(fp_source)
FILE *fp_source;
{
   int c, i;
   char line[MAXLINE + 1];

   while(TRUE)
   {                    /* does this line begin a preprocessor directive? */
      if( (c = filter_quotes(fp_source)) != '#' )
         return c;      /* no, return character */
                        /* yes, store until newline or EOF */
      if( (c = get_ppdir_line( fp_source, line )) == EOF )
         return EOF;
      if( strncmp( line, "define", 6 ) )        /* if not #define directive */
         continue;                              /* eat this line */
      if( line[ strlen(line) - 1 ] != '\\' )    /* if #define line ends */
         continue;                              /* with "\" */
      else
         while(TRUE)                            /* keep eating lines */
         {                                      /* which also end with "\" */
                                          /* store until newline or EOF */
            if( (c = get_ppdir_line( fp_source, line )) == EOF )
               return EOF;
                           /* done with this #define directive if this */
                           /* line is not also a continuation line */
            if( line[ strlen(line) - 1 ] != '\\' )
               break;
         }
   }
}

/* Utility routine used by filter_ppdir() -
/* read the character stream using filter_quotes, storing characters
 * in the parameter "line", until EOF or '\n' is encountered.
 * Return EOF or '\n' accordingly.
 */
int
get_ppdir_line(fp_source, line)
FILE *fp_source;
char *line;
{
   int i, c;
                                          /* store until newline or EOF */
   for( i = 0; i < MAXLINE && (c = filter_quotes(fp_source)) != '\n'
                                                         && c != EOF; i++ )
      line[i] = c;
   line[i] = 0;                           /* terminate string */
   if( c == EOF )
      return EOF;
   return '\n';
}

/* using the stream returned by filter_cmt() as input,
 * return a character stream in which any quoted character
 * or quoted string has been collapsed to the single special value QUOTES
 * to avoid considering special characters like '{', '}', '(', or ')'
 * which may occur within quotes.
 */
int
filter_quotes(fp_source)
FILE *fp_source;
{
   int c1, c2;

   if( (c1 = filter_cmt(fp_source)) != '\'' && c1 != '"' )
      return c1;     /* pass char through if not single or double quote */
   while( TRUE )
      switch( c2 = filter_cmt(fp_source) )
      {
         case '\\':                 /* beginning of an escape sequence */
            filter_cmt(fp_source);  /* so eat next char */
            break;
         case EOF:
            return EOF;
         default:
            if( c2 == c1 )          /* found end of quoted char or string */
               return QUOTES;
      }
}

/* Returns character stream, eating comments. */
/* Returns EOF if end of file. */
/* Nested comments are allowed. */
int
filter_cmt(fp_source)
FILE *fp_source;
{
   /* values for state */
   #define STABLE       0        /* not in process of changing the comment */
                                 /* level: i.e., not in the middle of a    */
                                 /* slash-star or star-slash combination.  */
   #define IN_CMT_FS    1        /* got '/', looking for '*' */
   #define OUT_CMT_STAR 2        /* got '*', looking for '/' */

   int c, state = STABLE, cmt_level = 0;

   while( TRUE )
   {
      c = fgetc(fp_source);
      if( c == EOF )
         return EOF;

      switch(state)
      {
         case STABLE:
            if( c == '*' )
               state = OUT_CMT_STAR;
            else if( c == '/' )
               state = IN_CMT_FS;
            break;

         case IN_CMT_FS:
            if( c == '*' )
            {
               state = STABLE;
               cmt_level++;            /* descend one comment level  */
               continue;
            }
            else if( !cmt_level )      /* if '/' not followed by '*' */
            {                          /* and outside any comment    */
               ungetc( c, fp_source ); /* push back this char        */
               return '/';             /* and return the '/'         */
            }
            else if( c != '/' )        /* stay in state IN_CMT_FS     */
               state = STABLE;         /* if next char is '/' as well */
            break;

         case OUT_CMT_STAR:
            if( c == '/' )
            {
               cmt_level--;         /* ascend one comment level */
               state = STABLE;
               continue;
            }
            else if( !cmt_level )      /* if '*' not followed by '/' */
            {                          /* and outside any comment    */
               ungetc( c, fp_source ); /* push back this char        */
               return '*';             /* and return the '*'         */
            }
            else if( c != '*' )        /* stay in state IN_CMT_FS     */
               state = STABLE;         /* if next char is '*' as well */
            break;
      }
      if( state == STABLE && !cmt_level )    /* if outside any comment */
         return c;                           /* return character */
   }
}



[LISTING TWO]


NAME:   bldfuncs - construct a table of C source file names together with
        a list of the names of all functions defined in these source files.

USAGE:  bldfuncs source_file_1 source_file_2 ...
        (wildcard characters are allowed)

DESCRIPTION:    bldfuncs is used to construct the list file needed by getf.
        The output file is named "funcs.txt", and has the format:

                source_file_1.c:
                        function_1
                        function_2
                        ...
                        function_n;

                source_file_2.c:
                        ...

        Of course this file could be constructed or modified using a
        text editor, but bldfuncs is designed to automate this process.
        bldfuncs is run at relatively infrequent intervals to update
        the list file, whereas getf is run frequently to locate functions.

EXAMPLES:       bldfuncs *.c            This will construct a funcs.txt file
                                        in the current directory which will
                                        contain a list of all .c files in
                                        that directory, together with a list
                                        of the functions defined in these
                                        files.

                bldfuncs *.c \source1\*.c \source2\*.c
                                        This will construct a funcs.txt file
                                        in the current directory which will
                                        contain the above information for
                                        the current directory, \source1,
                                        and \source2 combined.

                bldfuncs prog1.c prog2.c prog3.c
                                        This will construct a funcs.txt file
                                        in the current directory which will
                                        contain the above information for
                                        the selected files prog1.c, prog2.c
                                        and prog3.c combined.



[LISTING THREE]


/*
 * getf.c - locate the source file containing a specified function and
 *          present it in your favorite editor.
 * Copyright (c) 1988 Marvin Hymowech
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LINT_ARGS

#define TRUE      1
#define FALSE     0
#define LINE_LEN 256

   /* function declarations */
int patn_match(char *, char *);
void edit( char *, char * );
void ask_for_file(char **, char **, unsigned int);

unsigned int num_ctl_patns;      /* number of %s symbols in GETFEDIT var */
char *file_token, *func_token;
char arg1[64];                   /* argument for editor command line */
char *arg1_ctl;                  /* ctl string for building arg1 */
char *pgm_name;                  /* name of editor to invoke */

main(argc, argv)
int argc;
char **argv;
{
   char file_line[LINE_LEN], func_line[LINE_LEN];
   char func_name[64], edit_cmd[128];
   char *env_edit_cmd, *ctl_patn;
   unsigned int last_func, len, eof = FALSE;
   static char file_name[] = "funcs.txt"; /* input file name */
   FILE *funcs_file;
   static char delim[] = "\n\t ";   /* white space delimiters */
   #define MAX_CHOICES 256
   unsigned int num_choices = 0;
   char *func_choices[ MAX_CHOICES ], *file_choices[ MAX_CHOICES ];


   if( argc != 2 )
   {
      fprintf( stderr, "getf: wrong number of arguments" );
      exit(1);
   }

      /* the argument is the function name to find */
   strcpy( func_name, *++argv );

   if( (env_edit_cmd = getenv( "GETFEDIT" )) == NULL )
   {
      fprintf( stderr, "getf: missing environment variable GETFEDIT" );
      exit(1);
   }

      /* check if GETFEDIT environment variable has a place for
       * function name as well as file name - note function name
       * is assumed to go in the first %s pattern if there are
       * two %s patterns.
       */
   if( (ctl_patn = strstr( env_edit_cmd, "%s" )) == NULL )
   {
      fprintf( stderr,
               "getf: environment variable GETFEDIT has no %%s pattern" );
      exit(1);
   }

   num_ctl_patns = strstr( ++ctl_patn, "%s" ) == NULL ? 1 : 2;
   strcpy( edit_cmd, env_edit_cmd );
   if( (pgm_name = strtok( edit_cmd, " " )) == NULL )
   {
      fprintf( stderr,
               "getf: environment variable GETFEDIT has incorrect format" );
      exit(1);
   }
            /* point to argument following program name */
   arg1_ctl = edit_cmd + strlen(pgm_name) + 1;

   if( (funcs_file = fopen( file_name, "r" )) == NULL )
   {
      fprintf( stderr, "getf: can't open %s\n", *argv );
      exit(1);
   }

   while( !eof )     /* loop thru file names, which end with colon */
   {
      if( fgets( file_line, LINE_LEN, funcs_file ) == NULL )
         break;
                     /* bypass any line consisting of white space */
      if( (file_token = strtok( file_line, delim )) == NULL )
         continue;

      if( file_token[ len = strlen(file_token) - 1 ] != ':' )
      {
         fprintf(stderr, "getf: incorrect file format on %s", file_name);
         exit(1);
      }
      file_token[ len ] = 0;  /* kill trailing colon */
      last_func = FALSE;   /* set up to detect last func this file */

      while( !eof && !last_func )   /* loop thru func names this file */
      {                             /* last such ends with semicolon */
         if( fgets( func_line, LINE_LEN, funcs_file ) == NULL )
         {
            eof = TRUE;
            break;
         }
                     /* bypass any line consisting of white space */
         if( (func_token = strtok( func_line, delim )) == NULL )
            continue;

         if( func_token[ len = strlen(func_token) - 1 ] == ';' )
         {
            last_func = TRUE;          /* break loop after this one */
            func_token[ len ] = 0;     /* kill trailing semi-colon */
         }

         if( patn_match( func_name, func_token ) )
         {
            func_choices[num_choices] = strdup( func_token );
            file_choices[num_choices++] = strdup( file_token );
         }
      }
   }

   switch( num_choices )
   {
      case 0:
         fprintf( stderr, "getf: no match for %s in %s",
                                                      func_name, file_name );
         exit(1);
      case 1:
         edit( func_choices[0], file_choices[0] );
      default:
         ask_for_file(func_choices, file_choices, num_choices);
   }
}

/* return TRUE if string s matches pattern patn,
 * or FALSE if it does not.  Allowable wildcards
 * in patn are: ? for any one character, % for
 * any string of characters up to the next underscore
 * or end of string, and * for any string of characters
 * up to end of string.
 */
int
patn_match(patn, s)
char *patn;    /* pattern */
char *s;       /* string */
{
   for( ; *patn; patn++ )
   {
      if( !*s )               /* if out of s chars, no match */
         return                /* unless patn ends with * or % */
            ((*patn == '*' || *patn == '%') && !*(patn + 1) );

      switch( *patn )
      {
         case '%':
            while( *s != '_' && *s )
               s++;
            break;
         case '*':
            while( *s++ )
               ;
            break;
         case '?':
            s++;
            break;
         default:
            if( *s != *patn )
               return FALSE;
            s++;
      }
   }
   return *s == 0;
}

void
ask_for_file(func_choices, file_choices, num_choices)
char *func_choices[], *file_choices[];
unsigned int num_choices;
{
   int i;
   char line[LINE_LEN];

   while( TRUE )
   {
      printf( "Which one? (CR to exit)\n" );
      for( i = 1; i <= num_choices; i++ )
         printf( "\t%3d: %20.20s in %-30.30s\n", i, func_choices[i-1],
                                                         file_choices[i-1] );
      printf( "\nEnter number:" );
      fgets( line, LINE_LEN, stdin );

      if( line[0] == '\n' )
         break;
      if( (i = atoi(line)) < 1 || i > num_choices )
         printf( "\nInvalid choice!\007\n" );
      else
         edit( func_choices[i-1], file_choices[i-1] );
   }
}

void
edit(func, file)
char *func, *file;
{
            /* execlp will overlay this program with the editor */
   if( num_ctl_patns == 1 )
      sprintf( arg1, arg1_ctl, file );
   else
      sprintf( arg1, arg1_ctl, func, file );
   execlp( pgm_name, pgm_name, arg1, NULL);
                     /* if we're still here, print error msg */
   fprintf( stderr, "getf: exec failed" );
   exit(1);
}



[LISTING FOUR]


NAME:   getf - locate the source file containing a C function
               and present it in a specified editor.

USAGE:  getf [list_file_name] function name

DESCRIPTION:    getf looks for the file funcs.txt, which is
        presumed to be a listing of C source file names together
        with a list of the C functions defined in these source
        files; the expected format is

                source_file_1.c:
                        function_1
                        function_2
                        ...
                        function_n;

                source_file_2.c:
                        ...

        (This list file can be conveniently built using the companion
        program bldfuncs.c.)

                The editor to be used is specified in the DOS environment
        variable GETFEDIT, which is presumed to have the form:

                editor_name  control_string

        where control_string is an sprintf control string which may have
        either one or two occurrences of the pattern "%s": if one such
        pattern exist, it is replaced by the appropriate source file
        name in which the requested function resides; if two such
        patterns exist, the first "%s" is replaced by the function name
        and the second by the source file name.  This latter format allows
        the use of an editor-specific search command to position the
        source file to the requested function automatically.  After
        replacing the "%s" patterns as above, the GETFEDIT string is
        executed via a DOS exec call, thus overlaying the current program
        getf.

        The wildcards ?, * and % are allowed in the function name
        argument for getf, and are interpreted as: ? matches any single
        character, * matches the remainder of any string, and % matches
        all characters up to the next underscore or until the end of
        string.

EXAMPLES:  Assume the editor is BRIEF; possible GETFEDIT strings might be:

                b %s                    this will simply bring up the
                                        desired source file in BRIEF
                                        without any attempt at positioning
                                        the cursor to the requested function.

                b -m"search_fwd %s" %s  this will bring up the desired
                                        source file and position the cursor
                                        to the first occurrence of the
                                        requested function.

                b -m"funcsrch %s" %s    (see funcsrch.doc for an explanation
                                        of this customized BRIEF macro.)
                                        this will bring up the desired
                                        source file, seek to the end of
                                        the file, then seek backwards for
                                        the LAST occurrence of the requested
                                        function.  In practice, this is the
                                        approach most likely to position the
                                        cursor to the actual definition of
                                        the function.

        To illustrate wildcards:

                getf func*              matches func, func1, func_2;

                getf func%              mathces func, func1, but not func_2.

                getf func?_%_*          matches func1_new_, funca_old_stuff,
                                        but not func1_stuff.

NOTE:   Remember that to set GETFEDIT in your autoexec.bat file (or in
        any other batch file) you must use TWO percent signs instead of
        one:
                set GETFEDIT=b -m"funcsrch %%s" %%s

        This is necessary to avoid interpretation of the percent signs
        by command.com.








Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.