/*****************************************************************************
*   $Id: get.c,v 3.1 1997/05/13 05:34:26 darren Exp $
*
*   Copyright (c) 1996-1997, Darren Hiebert
*
*   Contains the high level source read functions (preprocessor directives
*   are handled within this level).
*****************************************************************************/

/*============================================================================
=   Include files
============================================================================*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "ctags.h"

/*============================================================================
=   Data declarations
============================================================================*/
typedef enum { COMMENT_NONE, COMMENT_C, COMMENT_CPLUS } Comment;

/*============================================================================
=   Data definitions
============================================================================*/

cppState Cpp = {
    0,
    {
	DRCTV_NONE,
	FALSE,
	{ 0, 0, "" },
	FALSE,
	0,
	{{FALSE,FALSE}}
    }
};

/*============================================================================
=   Function prototypes
============================================================================*/
static boolean cppReadDirective __ARGS((int c, char *name));
static boolean cppReadIdentifier __ARGS((int c, tagInfo *const tag));
static boolean pushCppIgnore __ARGS((const boolean ignore, const boolean pathChosen));
static boolean popCppIgnore __ARGS((void));
static boolean prevCppIgnore __ARGS((void));
static boolean setCppIgnore __ARGS((const boolean ignore));
static boolean wasPathChosen __ARGS((void));
static boolean handleDirective __ARGS((const int c));
static Comment isComment __ARGS((void));
static int skipOverCComment __ARGS((void));
static int skipOverCplusComment __ARGS((void));
static int skipToEndOfString __ARGS((void));
static int skipToEndOfChar __ARGS((void));

/*============================================================================
=   Function definitions
============================================================================*/

/*----------------------------------------------------------------------------
*   Scanning functions	
*
*   This section handles preprocessor directives.  It strips out all
*   directives and may emit a tag for #define directives.
*--------------------------------------------------------------------------*/

extern boolean cppOpen( name )
    const char *const name;
{
    boolean opened;

    opened = fileOpen(name);
    if (opened)
    {
	Cpp.ungetch = '\0';
	Cpp.directive.state   = DRCTV_NONE;
	Cpp.directive.accept  = TRUE;
	Cpp.directive.resolve = FALSE;
	Cpp.directive.level   = 0;
    }
    return opened;
}

extern void cppClose()
{
    fileClose();
}

/*  This puts a character back into the input queue for the source File.
 */
extern void cppUngetc( c )
    const int c;
{
    Cpp.ungetch = c;
}

/*  Reads a directive, whose first character is given by "c", into "name".
 */
static boolean cppReadDirective( c, name )
    int c;
    char *name;
{
    do
    {
	*name++ = c;
    } while (c = fileGetc(), (c != EOF  &&  isalpha(c)));
    fileUngetc(c);
    *name = '\0';					/* null terminate */

    return isspacetab(c);
}

/*  Reads an identifier, whose first character is given by "c", into "tag",
 *  together with the file location and corresponding line number.
 */
static boolean cppReadIdentifier( c, tag )
    int c;
    tagInfo *const tag;
{
    char *name = tag->name;

    do
    {
	*name++ = c;
    } while (c = fileGetc(), (c != EOF  &&  isident(c)));
    fileUngetc(c);
    *name = '\0';					/* null terminate */
    tag->location   = File.seek;
    tag->lineNumber = File.lineNumber;

    return (isspace(c)  ||  c == '(');
}

/*  Pushes one nesting level for an #if directive, indicating whether or not
 *  the path should be ignored and whether a path has already been chosen.
 */
static boolean pushCppIgnore( ignore, pathChosen )
    const boolean ignore;
    const boolean pathChosen;
{
    if (Cpp.directive.level < CppNestingLevel - 1)
    {
	++Cpp.directive.level;
	Cpp.directive.ifdef[Cpp.directive.level].ignore = ignore;
	Cpp.directive.ifdef[Cpp.directive.level].pathChosen = pathChosen;
    }
    return cppIgnore();
}

/*  Pops one nesting level for an #endif directive.
 */
static boolean popCppIgnore()
{
    if (Cpp.directive.level > 0)
	--Cpp.directive.level;

    return cppIgnore();
}

/*  Returns whether or not the parent of this nesting level was ignored.
 */
static boolean prevCppIgnore()
{
    boolean ignore;

    if (Cpp.directive.level <= 0)
	ignore = FALSE;
    else
	ignore = Cpp.directive.ifdef[Cpp.directive.level - 1].ignore;

    return ignore;
}

static boolean setCppIgnore( ignore )
    const boolean ignore;
{
    return Cpp.directive.ifdef[Cpp.directive.level].ignore = ignore;
}

static boolean wasPathChosen()
{
    return Cpp.directive.ifdef[Cpp.directive.level].pathChosen;
}

/*  Handles a pre-processor directive whose first character is given by "c".
 */
static boolean handleDirective( c )
    const int c;
{
    char *const name = Cpp.directive.tag.name;
    const tagScope scope = File.header ? SCOPE_GLOBAL : SCOPE_STATIC;
    boolean ignore = FALSE;

    switch (Cpp.directive.state)
    {
    case DRCTV_NONE:
	ignore = cppIgnore();		/* ignore characters until newline */
	break;

    case DRCTV_HASH:
	cppReadDirective(c, name);
	if (strcmp(name, "define") == 0)
	    Cpp.directive.state = DRCTV_DEFINE;
	else if (strncmp(name, "if", (size_t)2) == 0)
	    Cpp.directive.state = DRCTV_IF;
	else
	{
	    if (strcmp(name, "endif") == 0)
		ignore = popCppIgnore();
	    else if (Cpp.directive.resolve  &&  ! Option.braceFormat)
	    {
		if (strcmp(name, "elif") == 0)
		    ignore = setCppIgnore(TRUE);
		else if (strcmp(name, "else") == 0)
		    ignore = setCppIgnore(prevCppIgnore() || wasPathChosen());
	    }
	    else if (strcmp(name, "elif") == 0  ||  strcmp(name, "else") == 0)
		ignore = setCppIgnore(FALSE);

	    Cpp.directive.state = DRCTV_NONE;
	}
	break;

    case DRCTV_DEFINE:
	cppReadIdentifier(c, &Cpp.directive.tag);
	makeDefineTag(&Cpp.directive.tag, scope);
	Cpp.directive.state = DRCTV_NONE;
	break;

    case DRCTV_IF:
	if (Option.braceFormat)
	    ignore = pushCppIgnore(FALSE, TRUE);
	else if (c == '0')		/* special case: avoid first branch */
	    ignore = pushCppIgnore(TRUE, FALSE);
	else
	    ignore = pushCppIgnore(cppIgnore(), TRUE);
	Cpp.directive.state = DRCTV_NONE;
	break;
    }
    return ignore;
}

/*  Called upon reading of a slash ('/') characters, determines whether a
 *  comment is encountered, and its type.
 */
static Comment isComment()
{
    Comment comment;
    const int next = fileGetc();

    if (next == '*')
	comment = COMMENT_C;
    else if (next == '/')
	comment = COMMENT_CPLUS;
    else
    {
	fileUngetc(next);
	comment = COMMENT_NONE;
    }
    return comment;
}

/*  Skips over a C style comment. According to ANSI specification a comment
 *  is treated as white space, so we perform this subsitution.
 */
static int skipOverCComment()
{
    int c = fileGetc();

    while (c != EOF)
    {
	if (c != '*')
	    c = fileGetc();
	else
	{
	    const int next = fileGetc();

	    if (next != '/')
		c = next;
	    else
	    {
		c = ' ';			/* replace comment with space */
		break;
	    }
	}
    }
    return c;
}

/*  Skips over a C++ style comment.
 */
static int skipOverCplusComment()
{
    int c;

    while ((c = fileGetc()) != EOF)
    {
	if (c == BACKSLASH)
	    c = fileGetc();		/* throw away next character, too */
	else if (c == NEWLINE)
	    break;
    }
    return c;
}

/*  Skips to the end of a string, returning a special character to
 *  symbolically represent a generic string.
 */
static int skipToEndOfString()
{
    int c;

    while ((c = fileGetc()) != EOF)
    {
	if (c == BACKSLASH)
	    c = fileGetc();		/* throw away next character, too */
	else if (c == DOUBLE_QUOTE)
	    break;
	else if (c == NEWLINE)
	{
	    fileUngetc(c);
	    break;
	}
    }
    return STRING_SYMBOL;		/* symbolic representation of string */
}

/*  Skips to the end of the three (possibly four) 'c' sequence, returning a
 *  special character to symbolically represent a generic character.
 */
static int skipToEndOfChar()
{
    int c;

    while ((c = fileGetc()) != EOF)
    {
	if (c == BACKSLASH)
	    c = fileGetc();		/* throw away next character, too */
	else if (c == SINGLE_QUOTE)
	    break;
	else if (c == NEWLINE)
	{
	    fileUngetc(c);
	    break;
	}
    }
    return CHAR_SYMBOL;		    /* symbolic representation of character */
}

/*  This function returns the next character, stripping out comments,
 *  C pre-processor directives, and the contents of single and double
 *  quoted strings. In short, strip anything which places a burden upon
 *  the tokenizer.
 */
extern int cppGetc()
{
    Comment comment = COMMENT_NONE;
    boolean directive = FALSE;
    boolean ignore = FALSE;
    int c;

    if (Cpp.ungetch != '\0')
    {
	c = Cpp.ungetch;
	Cpp.ungetch = '\0';
	return c;	    /* return here to avoid re-calling debugPutc() */
    }
    else do
    {
	c = fileGetc();
	switch (c)
	{
	case EOF:
	    ignore	= FALSE;
	    directive	= FALSE;
	    break;
	case TAB:
	case SPACE:
	    break;				/* ignore most white space */
	case NEWLINE:
	    if (directive  &&  ! ignore)
		directive = FALSE;
	    Cpp.directive.accept = TRUE;
	    break;
	case DOUBLE_QUOTE:
	    Cpp.directive.accept = FALSE;
	    c = skipToEndOfString();
	    break;
	case '#':
	    if (Cpp.directive.accept)
	    {
		directive = TRUE;
		Cpp.directive.state = DRCTV_HASH;
		Cpp.directive.accept = FALSE;
	    }
	    break;
	case SINGLE_QUOTE:
	    Cpp.directive.accept = FALSE;
	    c = skipToEndOfChar();
	    break;
	case '/':
	    comment = isComment();
	    if (comment == COMMENT_C)
		c = skipOverCComment();
	    else if (comment == COMMENT_CPLUS)
	    {
		c = skipOverCplusComment();
		if (c == NEWLINE)
		    fileUngetc(c);
	    }
	    else
		Cpp.directive.accept = FALSE;
	    break;
	default:
	    Cpp.directive.accept = FALSE;
	    if (directive)
	    {
#ifdef DEBUG
		const boolean ignore0 = ignore;
#endif
		ignore = handleDirective(c);
#ifdef DEBUG
		if (ignore != ignore0)
		    debugCpp(ignore);
#endif
	    }
	    break;
	}
    } while (directive || ignore);

#ifdef DEBUG
    debugPutc(c, DEBUG_CPP);
#endif
    return c;
}

/* vi:set tabstop=8 shiftwidth=4: */
