/*****************************************************************************
*   $Id: entry.c,v 3.1 1997/05/13 05:34:26 darren Exp $
*
*   Copyright (c) 1996-1997, Darren Hiebert
*
*   Contains functions for creating tag entries.
*****************************************************************************/

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

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

/*  Note that the strings in this array must correspond to the types in the
 *  tagType enumeration.
 */
static const char *TagTypeNames[] = {
    "tag", "def", "enum", "proto", "func", "type", "var"
};

/*============================================================================
=   Function prototypes
============================================================================*/
static size_t writeSourceLine __ARGS((FILE *const fp, const char *const line));
static size_t writeCompactSourceLine __ARGS((FILE *const fp, const char *const line));
static void rememberMaxLengths __ARGS((const size_t nameLength, const size_t lineLength));
static void writeXrefEntry __ARGS((const tagInfo *const tag, const tagType type));
static void truncateTagLine __ARGS((char *const line, const char *const token, const boolean discardNewline));
static int writeEtagsEntry __ARGS((const tagInfo *const tag, const tagScope scope));
static int writeLineNumberEntry __ARGS((const tagInfo *const tag, const tagScope scope));
static int writePatternEntry __ARGS((const tagInfo *const tag, const tagScope scope, const tagType type));
static void writeTagEntry __ARGS((const tagInfo *const tag, const tagScope scope, const tagType type, const boolean useLineNumber));
static boolean includeTag __ARGS((const tagScope scope, const tagType type));
static void makeTagEntry __ARGS((const tagInfo *const tag, const tagScope scope, const tagType type, const boolean useLineNumber));

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

/*  This function copies the current line out some other fp.  It has no
 *  effect on the fileGetc() function.  During copying, any '\' characters
 *  are doubled and a leading '^' or trailing '$' is also quoted.  The '\n'
 *  character is not copied.  If the '\n' is preceded by a '\r', then the
 *  '\r' isn't copied.
 *
 *  This is meant to be used when generating a tag line.
 */
static size_t writeSourceLine( fp, line )
    FILE *const fp;
    const char *const line;
{
    size_t length = 0;
    const char *p;
    char c;

    /*	Write everything up to, but not including, the newline.
     */
    for (p = line, c = *p  ;  c != NEWLINE  &&  c != '\0'  ;  ++p, c = *p)
    {
	const int next = *(p + 1);

	/*  If character is '\', or a terminal '$', then quote it.
	 */
	if (c == BACKSLASH  ||  c == (Option.backward ? '?' : '/')  ||
	    (c == '$'  &&  next == NEWLINE))
	{
	    putc(BACKSLASH, fp);
	    ++length;
	}

	/*  Copy the character, unless it is a terminal '\r'.
	 */
	if (c != CRETURN  ||  next != NEWLINE)
	{
	    putc(c, fp);
	    ++length;
	}
    }
    return length;
}

/*  Writes "line", stripping leading and duplicate white space.
 */
static size_t writeCompactSourceLine( fp, line )
    FILE *const fp;
    const char *const line;
{
    boolean lineStarted = FALSE;
    size_t  length = 0;
    const char *p;
    char c;

    /*	Write everything up to, but not including, the newline.
     */
    for (p = line, c = *p  ;  c != NEWLINE  &&  c != '\0'  ;  c = *++p)
    {
	if (lineStarted  || ! isspace(c))	/* ignore leading spaces */
	{
	    lineStarted = TRUE;
	    if (isspace(c))
	    {
		int next;

		/*  Consume repeating white space.
		 */
		while (next = *(p+1) , isspace(next)  &&  next != NEWLINE)
		    ++p;
		c = ' ';	/* force space character for any white space */
	    }
	    if (c != CRETURN  ||  *(p + 1) != NEWLINE)
	    {
		putc(c, fp);
		++length;
	    }
	}
    }
    return length;
}

static void rememberMaxLengths( nameLength, lineLength )
    const size_t nameLength;
    const size_t lineLength;
{
    if (nameLength > TagFile.max.tag)
	TagFile.max.tag = nameLength;

    if (lineLength > TagFile.max.line)
	TagFile.max.line = lineLength;
}

static void writeXrefEntry( tag, type )
    const tagInfo *const tag;
    const tagType type;
{
    char line[MaxTagLine];
    size_t length;

    getFileLine(tag->location, line, MaxTagLine);
    length = fprintf(TagFile.fp, "%-20s %-6s %4ld  %-14s ",
		     tag->name, tagTypeName(type), tag->lineNumber, File.name);
    line[MaxTagLine - 2 - length] = '\0';		/* truncate line */
    length += writeCompactSourceLine(TagFile.fp, line);
    putc(NEWLINE, TagFile.fp);
    ++length;

    ++TagFile.numTags;
    rememberMaxLengths(strlen(tag->name), length);
}

/*  Truncates the text line containing the tag at the character following the
 *  tag, providing a character which designates the end of the tag.
 */
static void truncateTagLine( line, token, discardNewline )
    char *const line;
    const char *const token;
    const boolean discardNewline;
{
    char *p = strstr(line, token);

    if (p != NULL)
    {
	p += strlen(token);
	if (*p != '\0'  &&  !(*p == '\n'  &&  discardNewline))
	    ++p;		/* skip past character terminating character */
	*p = '\0';
    }
}

static int writeEtagsEntry( tag, scope )
    const tagInfo *const tag;
    const tagScope scope;
{
    char line[MaxTagLine];
    int length;

    getFileLine(tag->location, line, MaxTagLine);
    truncateTagLine(line, tag->name, TRUE);
    if (Option.include.prefix  &&  scope == SCOPE_STATIC)
	length = fprintf(TagFile.etags.fp, "%s\177%s:%s\001%ld,%ld\n", 
			 line, File.name, tag->name,
			 tag->lineNumber, tag->location);
    else
	length = fprintf(TagFile.etags.fp, "%s\177%ld,%ld\n", 
			 line, tag->lineNumber, tag->location);
    TagFile.etags.byteCount += length;
    return length;
}

static int writeLineNumberEntry( tag, scope )
    const tagInfo *const tag;
    const tagScope scope;
{
    int length = 0;

    if (scope == SCOPE_STATIC  &&  Option.include.prefix)
	length += fprintf(TagFile.fp, "%s:", File.name);

    length += fprintf(TagFile.fp, "%s\t%s\t%ld",
		      tag->name, File.name, tag->lineNumber);

    if (scope == SCOPE_STATIC  &&  Option.include.extensions)
	length += fprintf(TagFile.fp, ";\"\tfile:");

    length += fprintf(TagFile.fp, "\n");

    return length;
}

static int writePatternEntry( tag, scope, type )
    const tagInfo *const tag;
    const tagScope scope;
    const tagType type;
{
    const int searchChar = Option.backward ? '?' : '/';
    char line[MaxTagLine];
    boolean newlineTerminated;
    int length = 0;

    getFileLine(tag->location, line, MaxTagLine);
    if (type == TAG_DEFINE)
	truncateTagLine(line, tag->name, FALSE);
    newlineTerminated = (line[strlen(line) - 1] == '\n');

    if (scope == SCOPE_STATIC  &&  Option.include.prefix)
	length += fprintf(TagFile.fp, "%s:", File.name);

    length += fprintf(TagFile.fp, "%s\t%s\t", tag->name, File.name);
    length += fprintf(TagFile.fp, "%c^", searchChar);
    length += writeSourceLine(TagFile.fp, line);
    length += fprintf(TagFile.fp, "%.*s%c", MaxTagLine - 3 - length,
		      newlineTerminated ? "$":"", searchChar);

    if (scope == SCOPE_STATIC  &&  Option.include.extensions)
	length += fprintf(TagFile.fp, ";\"\tfile:");

    length += fprintf(TagFile.fp, "\n");

    return length;
}

static void writeTagEntry( tag, scope, type, useLineNumber )
    const tagInfo *const tag;
    const tagScope scope;
    const tagType type;
    const boolean useLineNumber;
{
    size_t length;

    if (Option.etags)
	length = writeEtagsEntry(tag, scope);
    else if (useLineNumber)
	length = writeLineNumberEntry(tag, scope);
    else
	length = writePatternEntry(tag, scope, type);

    ++TagFile.numTags;
    rememberMaxLengths(strlen(tag->name), length);
}

static boolean includeTag( scope, type )
    const tagScope scope;
    const tagType type;
{
    boolean include;

    if (scope == SCOPE_EXTERN  ||		    /* should never happen */
	(scope == SCOPE_STATIC  &&  ! Option.include.statics))
    {
	include = FALSE;
    }
    else
    {
	switch (type)
	{
	case TAG_BLOCKTAG:  include = Option.include.blockTags;	    break;
	case TAG_DEFINE:    include = Option.include.defines;	    break;
	case TAG_ENUM:	    include = Option.include.enumValues;    break;
	case TAG_FUNCTION:  include = Option.include.functions;	    break;
	case TAG_FUNCDECL:  include = Option.include.prototypes;    break;
	case TAG_TYPEDEF:   include = Option.include.typedefs;	    break;
	case TAG_VARIABLE:  include = Option.include.variables;	    break;
	default:	    include = FALSE;			    break;
	}
    }
    return include;
}

/*  This function generates a tag for the object in name, whose tag line is
 *  located at a given seek offset.
 */
static void makeTagEntry( tag, scope, type, useLineNumber )
    const tagInfo *const tag;
    const tagScope scope;
    const tagType type;
    const boolean useLineNumber;
{
    if (includeTag(scope, type))
    {
	if (Option.xref)
	    writeXrefEntry(tag, type);
	else
	    writeTagEntry(tag, scope, type, useLineNumber);
#ifdef DEBUG
	debugEntry(scope, type, tag->name);
#endif
    }
}

extern void makeTag( tag, scope, type )
    const tagInfo *const tag;
    const tagScope scope;
    const tagType type;
{
    makeTagEntry(tag, scope, type, (Option.locate == EX_LINENUM));
}

extern void makeDefineTag( tag, scope )
    const tagInfo *const tag;
    const tagScope scope;
{
    makeTagEntry(tag, scope, TAG_DEFINE, (Option.locate != EX_PATTERN));
}

extern const char *tagTypeName( type )
    const tagType type;
{
    const char *name;

    if (type < TAG_NUMTYPES)
	name = TagTypeNames[type];
    else
	name = "?";

    return name;
}

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