/*****************************************************************************
*   $Id: read.c,v 3.1 1997/05/13 05:34:26 darren Exp $
*
*   Copyright (c) 1996-1997, Darren Hiebert
*
*   Conains low level source file read functions (line splicing and newline
*   conversion are performed at this level).
*****************************************************************************/

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

/*  To declare "struct stat" and stat().
 */
#if __MWERKS__
# include <stat.h>	    /* there is no sys directory on the Mac */
#else
# include <sys/types.h>
# include <sys/stat.h>
#endif

#include "ctags.h"

/*============================================================================
=   Defines
============================================================================*/

/*----------------------------------------------------------------------------
*-	Portability
----------------------------------------------------------------------------*/

/*  These might not be defined.
 */
#ifndef S_ISREG
# if defined(S_IFREG) && !defined(AMIGA)
#  define S_ISREG(mode)	    ((mode) & S_IFREG)
# else
#  define S_ISREG(mode)	    TRUE	/* assume regular file */
# endif
#endif

#ifndef S_ISLNK
# ifdef S_IFLNK
#  define S_ISLNK(mode)	    (((mode) & S_IFMT) == S_IFLNK)
# else
#  define S_ISLNK(mode)	    FALSE	/* assume no soft links */
# endif
#endif

/*----------------------------------------------------------------------------
*-	Miscellaneous
----------------------------------------------------------------------------*/

#define is_file(st)	(S_ISLNK((st)->st_mode) || S_ISREG((st)->st_mode))

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

sourceFile File = { NULL, NULL, 0, -1, FALSE, 0, FALSE, FALSE };

/*============================================================================
=   Function prototypes
============================================================================*/

static boolean isFileHeader __ARGS((const char *const name));
static void fileNewline __ARGS((void));

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

/*  Determines whether the specified file name is considered to be a header
 *  file for the purposes of determining whether enclosed tags are global or
 *  static.
 */
static boolean isFileHeader( name )
    const char *const name;
{
    boolean header = FALSE;		    /* default unless match found */
    const char *extension;

    extension = strrchr(name, '.');	    /* find last '.' */
    if (extension != NULL)
    {
	int i;

	++extension;			    /* skip to character after '.' */
	for (i = 0 ; Option.headerExt[i] != NULL ; ++i)
	{
	    if (strcmp(Option.headerExt[i], extension) == 0)
	    {
		header = TRUE;		    /* found in list */
		break;
	    }
	}
    }
    return header;
}

/*  This function opens a file, and resets the line counter.  If it fails,
 *  it will display an error message and leave the File.fp set to NULL.
 */
extern boolean fileOpen( name )
    const char *const name;
{
    boolean opened = FALSE;
    struct stat file_status;

    /*	If another file was already open, then close it.
     */
    if (File.fp != NULL)
    {
	fclose(File.fp);			/* close any open source file */
	File.fp = NULL;
    }

    if (stat(name, &file_status) != 0)
	perror(name);
    else if (is_file(&file_status))
    {
	File.fp = fopen(name, "rb");	/* must be binary mode for fseek() */
	if (File.fp == NULL)
	    perror(name);
	else
	{
	    opened = TRUE;
	    File.name	    = name;
	    File.lineNumber = 0L;
	    File.seek	    = 0L;
	    File.afterNL    = TRUE;
	    File.warned	    = FALSE;

	    if (strlen(name) > TagFile.max.file)
		TagFile.max.file = strlen(name);

	    /*	Determine whether this is a header File.
	     */
	    File.header = isFileHeader(name);
#ifdef DEBUG
	    debugOpen(name);
#endif
	}
    }
    return opened;
}

extern void fileClose()
{
    if (File.fp != NULL)
    {
	fclose(File.fp);
	File.fp = NULL;
    }
}

/*  Action to take for each encountered source newline.
 */
static void fileNewline()
{
    File.afterNL = FALSE;
    File.seek	 = ftell(File.fp);
    File.lineNumber++;
#ifdef DEBUG
    if (Option.breakLine == File.lineNumber) lineBreak();
#endif
}

/*  This function reads a single character from the stream. 
 */
extern int fileGetc()
{
    boolean escaped = FALSE;
    int	c;

    /*	If there is an ungotten character, then return it.  Don't do any
     *	other processing on it, though, because we already did that the
     *	first time it was read.
     */
    if (File.ungetch != '\0')
    {
	c = File.ungetch;
	File.ungetch = '\0';
	return c;	    /* return here to avoid re-calling debugPutc() */
    }

nextChar:	/* not structured, but faster for this critical path */

    /*	If previous character was a newline, then we're starting a line.
     */
    if (File.afterNL)
	fileNewline();

    c = getc(File.fp);
    switch (c)
    {
    default:
	if (escaped)
	{
	    ungetc(c, File.fp);		/* return character after BACKSLASH */
	    c = BACKSLASH;
	}
	break;

    case BACKSLASH:				/* test for line splicing */
	if (escaped)
	    ungetc(c, File.fp);			/* push back one just read */
	else
	{
	    escaped = TRUE;		/* defer test until next character */
	    goto nextChar;
	}
	break;

    /*	The following cases turn line breaks into a canonical form. The three
     *	commonly used forms if line breaks: LF (UNIX), CR (MacIntosh), and
     *	CR-LF (MS-DOS) are converted into a generic newline.
     */
    case CRETURN:
	{
	    const int next = getc(File.fp);	/* is CR followed by LF? */

	    /*	If this is a carriage-return/line-feed pair, treat it as one
	     *	newline, throwing away the line-feed.
	     */
	    if (next != NEWLINE)
		ungetc(next, File.fp);
	}
	c = NEWLINE;				/* convert CR into newline */
    case NEWLINE:
	File.afterNL = TRUE;
	if (escaped)				/* check for line splicing */
	{
#ifdef DEBUG
	    debugPutc(BACKSLASH, DEBUG_VISUAL);	    /* print the characters */
	    debugPutc(c, DEBUG_VISUAL);		    /*   we are throwing away */
#endif
	    escaped = FALSE;		    /* BACKSLASH now fully processed */
	    goto nextChar;		    /* through away "\NEWLINE" */
	}
	break;
    }

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

extern void fileUngetc( c )
    int c;
{
    File.ungetch = c;
}

/*  Places into "line_buffer" the contents of the line referenced by "seek".
 */
extern void getFileLine( seek, line_buffer, maxLength )
    const long seek;
    char *const line_buffer;
    const unsigned int maxLength;
{
    const long oldseek = ftell(File.fp);    /* remember original position */

    fseek(File.fp, seek, SEEK_SET);
    fgets(line_buffer, (int)maxLength, File.fp);	
    fseek(File.fp, oldseek, SEEK_SET);	    /* return to original position */
}

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