/**************************************************************
 *
 *      GenTags.c - Generate tags for the supplied file
 *
 *      Copyright (c) 1989, Peter Cherna
 *
 *      Created:  August 28, 1989
 *      Modified: August 28, 1989       Release 1.2:  August 29, 1989
 *
 *      Auto: cc -q -o RAM:<file>.o <path><file>
 *      Auto: ln RAM:<file>.o -lc -o <path><file>
 *
 **************************************************************/

/*  Known bugs:  If the source file contains an open comment
    _INSIDE QUOTATION MARKS_, GenTags thinks that the source is
    actually in comments until the next close.  That's the only
    one I know of at the moment. */

#include <functions.h>
#include <ctype.h>
#include <stdio.h>

#define MAXNAME 80

/*  cmode is the comment mode, and means we have found half of an open
    or close comment, and are looking for the other half: */
#define OPENQ           1
#define CLOSEQ          2

/*  mode is the status of the search, and could mean we have found the
    open paren. of a possible function, or the close paren. of a possible
    function: */
#define LOOKING         0       /*  Just looking for names */
#define GETTING_NAME    1       /*  Building a name */
#define GOT_NAME        2       /*  Got a name, waiting for open bracket */
#define GOT_OPEN        3       /*  Got open bracket, waiting for close */
#define GOT_CLOSE       4       /*  Got close, looking to see what happens */

/*  I #define these so their occurrences don't mess up my editor's bracket
    matching routines, which don't recognize quoted strings */
#define OBRACE '{'
#define CBRACE '}'

main(argc, argv)

int argc;
char *argv[];

    {
    char *filename;     /*  Name of file to tag */
    FILE *source;       /*  file handle */
    int comment;        /*  =1 if in a comment */
    int brace;          /*  Nest count of braces */
    int mode;           /*  Indicates how much of a function we've found */
    int cmode;          /*  Indicates how much of an open/close comment found */
    int notagline;      /*  We don't check on lines that have '"', '#', or '='
                            on them before any candidate function, since these
                            could be other things, like quoted strings,
                            #defines, or constant expressions involving
                            brackets.  We also handle terminating backslashes
                            as continuations of quotes or #defines */
    int line;           /*  line # we are processing */
    int fline;          /*  line # were we found the last candidate for
                            function name */
    int i;              /*  running index for candidate function name string */
    char funcname[MAXNAME]; /*  candidate function name string under construction */
    char c;             /*  character under consideration */
    char prevc;         /*  previous character considered */

    if (argc == 1)
        {
        printf("GenTags - Copyright (c) 1989, Peter Cherna");
        printf("    Usage:  %s sourcefile", argv[0]);
        printf("Generates tags for the specified sourcefile.\n");
        exit(0);
        }
    else if (argc == 0)
        exit(0);

    filename = argv[1];
    source = fopen(filename, "r");
    comment = 0;
    brace = 0;
    mode = LOOKING;
    cmode = 0;
    notagline = 0;
    line = 1;
    c = NULL;

    while (!feof(source))
        {
        prevc = c;
        c = agetc(source);
        if (c == '\n')
            {
            line++;
            /*  If the previous line involved a quote or a hash, and it
                ended with a backslash, then assume we're on a continuation
                of a string or #define. */
            if (prevc != '\\')
                notagline = 0;
            }

        /*   If in a comment, check to see if we're leaving: */
        if (comment)
            {
            if (cmode == CLOSEQ)
                {
                cmode = 0;
                if (c == '/')
                    comment = 0;
                }
            if (c == '*')
                /*  We may be beginning to leave the comment ... */
                cmode = CLOSEQ;
            }
        else
            {
            /*  Not in a comment */
            if (isalnum(c) || (c == '_'))
                {
                /*  If this is a legal character for a C name, and
                    we're just looking, then start getting the name.
                    If we had a name, then start over.
                    Note down the line number in case we are picking
                    up a function name: */
                if ((mode == LOOKING) || (mode == GOT_NAME))
                    {
                    fline = line;
                    i = 0;
                    funcname[i++] = c;
                    mode = GETTING_NAME;
                    }
                /*  If we're assembling a name, keep going: */
                else if ((mode == GETTING_NAME) && (i < MAXNAME))
                        funcname[i++] = c;
                }
            else if ((mode == GETTING_NAME) || (mode == GOT_NAME))
                {
                /*  If we were getting a name, and now there's a character
                    that isn't part of the name, it had better be a blank,
                    a comment, or an open bracket, or else we start looking
                    again: */
                if ((!isspace(c)) && (c != '/') && (c != '('))
                    mode = LOOKING;
                else
                    {
                    funcname[i] = '\0';
                    mode = GOT_NAME;
                    }
                }

            /*  If there are any quote, hash, or equals characters before
                anything that resembles a function, then we presume that the 
                'function' is really part of a quoted string, #define,
                or equation, and should not be tagged */
            if ((c == '\"') || (c == '#') || (c == '='))
                notagline = 1;

            if ((cmode == OPENQ) && (c == '*'))
                {
                /*   Got an open comment */
                cmode = 0;
                comment = 1;
                }
            else
                {
                if (cmode == OPENQ)
                    /*  Was looking for open comment, but did not find it.
                        This guarantees that this is not a function. */

                    mode = LOOKING;

                /*  Handle brace nest count: */
                if (c == OBRACE)
                    brace++;
                else if (c == CBRACE)
                    brace--;

                if (c == '/')
                    /*  This may be the beginning of an open comment */
                    cmode = OPENQ;
                else
                    cmode = 0;

                if ((mode == GOT_OPEN) && (c == ')'))
                    /*  If we had the open bracket, mow we've got the close
                        bracket */
                    mode = GOT_CLOSE;
                else if (mode == GOT_CLOSE)
                    {
                    if ((c == ';') || (c == '=') || (c == ','))
                    /*  Found a semi-colon, equals, or comma before anything
                        else of substance so we have either a function
                        declaration, a function pointer initialization,
                        or a list of function declarations: */
                        mode = LOOKING;
                    else if ((cmode != OPENQ) && (!isspace(c)))
                        /*  We defer judgement if we found a (maybe) open
                            comment or a blank character, otherwise we
                            know that this is an actual function: */
                        {
                        printf("%s; %s; %d\n", funcname, filename, fline);
                        mode = LOOKING;
                        }
                    }

                else if ((mode == GOT_NAME) && (c == '(') && (!brace) &&
                    (!notagline))
                    /*  We spotted an open bracket, and the conditions
                        make it possible that it is a function definition
                        or declaration */
                    mode = GOT_OPEN;
                }
            }
        }
    fclose(source);
    }
