/****************************************************************************
*
*                            Open Watcom Project
*
*    Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved.
*
*  ========================================================================
*
*    This file contains Original Code and/or Modifications of Original
*    Code as defined in and that are subject to the Sybase Open Watcom
*    Public License version 1.0 (the 'License'). You may not use this file
*    except in compliance with the License. BY USING THIS FILE YOU AGREE TO
*    ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is
*    provided with the Original Code and Modifications, and is also
*    available at www.sybase.com/developer/opensource.
*
*    The Original Code and all software distributed under the License are
*    distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
*    EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM
*    ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF
*    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR
*    NON-INFRINGEMENT. Please see the License for the specific language
*    governing rights and limitations under the License.
*
*  ========================================================================
*
* Description:  processing file/macro input data.
*
****************************************************************************/

#include <ctype.h>
//#include <stdarg.h> /* v2.12: removed - was necessary for _splitpath()/_makepath() */

#include "globals.h"
#include "memalloc.h"
#include "parser.h"
#include "macro.h"
#include "input.h"
#include "lqueue.h"
#include "myassert.h"

#define DETECTCTRLZ 1 /* 1=Ctrl-Z in input stream will skip rest of the file */

/* FILESEQ: if 1, stores a linked list of source files, ordered
 * by usage. Masm stores such a list in the COFF symbol table
 * when -Zd/-Zi is set. It isn't necessary, however, and JWasm's
 * COFF code currently will ignore the list.
 */

#define FILESEQ 0

char   *commentbuffer;

struct asym *FileCur; /* @FileCur symbol, created in SymInit() */
struct asym *LineCur; /* @Line symbol, created in SymInit()    */

enum src_item_type {
    SIT_FILE,
    SIT_MACRO,
};

/* item on src stack ( contains currently open source files & macros ) */
struct src_item {
    struct src_item     *next;
    uint_8              type;       /* item type ( see enum src_item_type ) */
    uint_16             srcfile;    /* index of file in ModuleInfo.g.FNames */
    union {
        void            *content;   /* generic */
        FILE            *file;      /* if item is a file */
        struct macro_instance *mi;  /* if item is a macro */
    };
    uint_32             line_num;   /* current line # */
    /* v2.11 field macro moved to struct macro_instance */
    //struct asym         *macro;     /* the symbol if it is a macro */
};

/* v2.11: introduced a list of unused src_items */
static struct src_item *SrcFree;

#define src_stack  ModuleInfo.g.src_stack

#if FILESEQ
struct qdesc            FileSeq;
#endif

#ifdef DEBUG_OUT
struct asm_tok *end_tokenarray;
char           *end_stringbuf;
static int_32 cntflines;  /* count file lines ( read by fgets() ) */
static int_32 cntlines;   /* count lines read by GetTextLine() */
extern int_32 cnttok0;    /* count Tokenize() calls, index==0 */
extern int_32 cnttok1;    /* count Tokenize() calls, index!=0 */
extern int_32 cntppl0;    /* count preprocessed lines 1 */
extern int_32 cntppl1;    /* count preprocessed lines 2 */
extern int_32 cntppl2;    /* count lines NOT handled by preprocessor */
#endif

/* buffer for source lines
 * since the lines are sometimes concatenated
 * the buffer must be a multiple of MAX_LINE_LEN
 */
static char *srclinebuffer;

/* string buffer - token strings and other stuff are stored here.
 * must be a multiple of MAX_LINE_LEN since it is used for string expansion.
 */
char *token_stringbuf;  /* start token string buffer */

/* fixme: add '|| defined(__CYGWIN__)' ? */
#if defined(__UNIX__)

#define INC_PATH_DELIM      ':'
#define INC_PATH_DELIM_STR  ":"
#define DIR_SEPARATOR       '/'
#define filecmp strcmp
#define ISPC( x ) ( x == '/' )
#define ISABS( x ) ( *x == '/' )

#else

#define INC_PATH_DELIM      ';'
#define INC_PATH_DELIM_STR  ";"
#define DIR_SEPARATOR       '\\'
#define filecmp _stricmp
#define ISPC( x ) ( x == '/' || x == '\\' || x == ':' )
#define ISABS( x ) ( *x == '/' || *x == '\\' || ( *x &&  *(x+1) == ':' && ( *(x+2) == '/' || *(x+2) == '\\' ) ) )

#endif

#if 0 /* v2.11: obsolete, field fullname removed */
static char *GetFullPath( const char *name, char *buff, size_t max )
/******************************************************************/
{
    char        *p;

    p = _fullpath( buff, name, max );
    if( p == NULL )
        p = (char *)name;

#if defined(__UNIX__)
    if( (p[0] == '/' && p[1] == '/') && (name[0] != '/' || name[1] != '/') ) {
        /*
         * if the _fullpath result has a node number and
         * the user didn't specify one, strip the node number
         * off before returning
         */
        p += 2;
        while( *(p++) != '/' ) ;
    }
#endif
    return( p );
}
#endif

/* v2.12: function added - _splitpath()/_makepath() removed */

const char *GetFNamePart( const char *fname )
/*******************************************/
{
    const char *rc;
    for ( rc = fname; *fname; fname++ )
        if ( ISPC( *fname ) )
            rc = fname + 1;
    return( rc );
}

/* fixme: if the dot is at pos 0 of filename, ignore it */

char *GetExtPart( const char *fname )
/***********************************/
{
    char *rc;
    for( rc = NULL; *fname; fname++ ) {
        if( *fname == '.' ) {
            rc = (char *)fname;
        } else if( ISPC( *fname ) ) {
            rc = NULL;
        }
    }
    return( rc ? rc : (char *)fname );
}

/* check if a file is in the array of known files.
 * if no, store the file at the array's end.
 * returns array index.
 * used for the main source and all INCLUDEd files.
 * the array is stored in the standard C heap!
 * the filenames are stored in the "local" heap.
 */
static unsigned AddFile( char const *fname )
/******************************************/
{
    unsigned    index;

    DebugMsg1(("AddFile(%s) enter, curr index=%u\n", fname, ModuleInfo.g.cnt_fnames ));
    for( index = 0; index < ModuleInfo.g.cnt_fnames; index++ ) {
        if( filecmp( fname, ModuleInfo.g.FNames[index].fname ) == 0 ) {
#ifdef DEBUG_OUT
            if ( Parse_Pass == PASS_1 )
                ModuleInfo.g.FNames[index].included++;
#endif
            return( index );
        }
    }

    if ( ( index % 64 ) == 0 ) {
        struct fname_item *newfn;
        newfn = (struct fname_item *)MemAlloc( ( index + 64 ) * sizeof( struct fname_item ) );
        if ( ModuleInfo.g.FNames ) {
            memcpy( newfn, ModuleInfo.g.FNames, index * sizeof( struct fname_item ) );
            MemFree( ModuleInfo.g.FNames );
        }
        ModuleInfo.g.FNames = newfn;
    }
    ModuleInfo.g.cnt_fnames++;

    /* v2.11: use name directly - allows COFF .file entries with relative paths */
    //_splitpath( fname, NULL, NULL, name, ext );

    ModuleInfo.g.FNames[index].fname = (char *)LclAlloc( strlen( fname ) + 1 );
    strcpy( ModuleInfo.g.FNames[index].fname, fname );
    /* v2.11: field fullname removed */
    //ModuleInfo.g.FNames[index].fullname = (char *)LclAlloc( strlen( fullname ) + 1 );
    //strcpy( ModuleInfo.g.FNames[index].fullname, fullname );
    DebugCmd( ModuleInfo.g.FNames[index].included = 1 );
    return( index );
}

const struct fname_item *GetFName( unsigned index )
/*************************************************/
{
    return( ModuleInfo.g.FNames+index );
}

/* free the file array.
 * this is done once for each module after the last pass.
 */

static void FreeFiles( void )
/***************************/
{
#if FASTMEM==0
    int i;

    /* remove the main src item from src stack */
    src_stack->next = SrcFree;
    SrcFree = src_stack;
#endif
    /* v2.03: set src_stack=NULL to ensure that GetCurrSrcPos()
     * won't find something when called from main().
     */
    src_stack = NULL;

#if FASTMEM==0
    /* free the "free src_items"-heap */
    while ( SrcFree ) {
        struct src_item *next;
        next = SrcFree->next;
        LclFree( SrcFree );
        SrcFree = next;
    };
    for ( i = 0; i < ModuleInfo.g.cnt_fnames; i++ ) {
        LclFree( ModuleInfo.g.FNames[i].fname );
        //LclFree( ModuleInfo.g.FNames[i].fullname );
    }
#endif
    if ( ModuleInfo.g.FNames ) {
        MemFree( ModuleInfo.g.FNames );
        ModuleInfo.g.FNames = NULL;
    }
    return;
}

/* clear input source stack (include files and open macros).
 * This is done after each pass.
 * Usually the stack is empty when the END directive occurs,
 * but it isn't required that the END directive is located in
 * the main source file. Also, an END directive might be
 * simulated if a "too many errors" condition occurs.
 */

void ClearSrcStack( void )
/************************/
{
    struct src_item   *nextfile;

    DeleteLineQueue();

    /* dont close the last item (which is the main src file) */
    for( ; src_stack->next ; src_stack = nextfile ) {
        nextfile = src_stack->next;
        if ( src_stack->type == SIT_FILE ) {
            fclose( src_stack->file );
        }
        //LclFree( src_stack );
        src_stack->next = SrcFree;
        SrcFree = src_stack;
    }
    return;
}

/* get/set value of predefined symbol @Line */

void UpdateLineNumber( struct asym *sym, void *p )
/************************************************/
{
    struct src_item *curr;
    for ( curr = src_stack; curr ; curr = curr->next )
        if ( curr->type == SIT_FILE ) {
            sym->value = curr->line_num;
            break;
        }
    return;
}

uint_32 GetLineNumber( void )
/***************************/
{
    UpdateLineNumber( LineCur, NULL );
    return( LineCur->uvalue );
}

#ifdef DEBUG_OUT

extern unsigned GetLqLine( void );

char *GetTopLine( char *buffer )
/******************************/
{
    *buffer = NULLC;
    if ( ModuleInfo.GeneratedCode )
        sprintf( buffer, "(%u)", GetLqLine() );
    else if( src_stack->type == SIT_MACRO )
        sprintf( buffer, "[%s.%" I32_SPEC "u]", src_stack->mi->macro->name, src_stack->line_num );
    return( buffer );
}

char *GetTopSrcName( void )
/*************************/
{
    if ( src_stack->type == SIT_MACRO )
        return( src_stack->mi->macro->name );
    return( GetFName( src_stack->srcfile )->fname );
}

#endif

/* read one line from current source file.
 * returns NULL if EOF has been detected and no char stored in buffer
 * v2.08: 00 in the stream no longer causes an exit. Hence if the
 * char occurs in the comment part, everything is ok.
 */
static char *my_fgets( char *buffer, int max, FILE *fp )
/******************************************************/
{
    char        *ptr = buffer;
    char        *last = buffer + max;
    int         c;

    c = getc( fp );
    while( ptr < last ) {
        switch ( c ) {
        case '\r':
            break; /* don't store CR */
        case '\n':
            /* fall through */
        //case '\0': /* v2.08: */
#ifdef DEBUG_OUT
            if ( Parse_Pass == PASS_1 )
                cntflines++;
#endif
            *ptr = NULLC;
            return( buffer );
#if DETECTCTRLZ
        case 0x1a:
            /* since source files are opened in binary mode, ctrl-z
             * handling must be done here.
             */
            /* no break */
#endif
        case EOF:
            *ptr = NULLC;
            return( ptr > buffer ? buffer : NULL );
        default:
            *ptr++ = c;
        }
        c = getc( fp );
    }
    EmitErr( LINE_TOO_LONG );
    *(ptr-1) = NULLC;
    return( buffer );
}

#if FILESEQ
void AddFileSeq( unsigned file )
/******************************/
{
    struct file_seq *node;
    node = LclAlloc( sizeof( struct file_seq ) );
    node->next = NULL;
    node->file = file;
    if ( FileSeq.head == NULL )
        FileSeq.head = FileSeq.tail = node;
    else {
        ((struct file_seq *)FileSeq.tail)->next = node;
        FileSeq.tail = node;
    }
}
#endif

/* push a new item onto the source stack.
 * type: SIT_FILE or SIT_MACRO
 */
static struct src_item *PushSrcItem( char type, void *pv )
/********************************************************/
{
    struct src_item   *curr;

    if ( SrcFree ) {
        curr = SrcFree;
        SrcFree = curr->next;
    } else
        curr = LclAlloc( sizeof( struct src_item ) );
    curr->next = src_stack;
    src_stack = curr;
    curr->type = type;
    curr->content = pv;
    curr->line_num = 0;
    return( curr );
}

/* push a macro onto the source stack. */

void PushMacro( struct macro_instance *mi )
/*****************************************/
{
    DebugMsg1(( "PushMacro(%s)\n", mi->macro->name ));
    PushSrcItem( SIT_MACRO, mi );
    return;
}

#if FASTMEM==0
bool MacroInUse( struct dsym *macro )
/***********************************/
{
    struct src_item *curr;

    for ( curr = src_stack; curr ; curr = curr->next )
        if ( curr->type == SIT_MACRO && curr->mi->macro == &macro->sym )
            return( TRUE );

    return( FALSE );
}
#endif

unsigned get_curr_srcfile( void )
/*******************************/
{
    struct src_item *curr;
    for ( curr = src_stack; curr ; curr = curr->next )
        if ( curr->type == SIT_FILE )
            return( curr->srcfile );
    return( ModuleInfo.srcfile );
}

#if FASTPASS
void set_curr_srcfile( unsigned file, uint_32 line_num )
/******************************************************/
{
    if ( file != 0xFFF ) /* 0xFFF is the special value for macro lines */
        src_stack->srcfile = file;
    src_stack->line_num = line_num;
    return;
}
#endif

void SetLineNumber( unsigned line )
/*********************************/
{
    src_stack->line_num = line;
    return;
}

/* for error listing, render the current source file and line */
/* this function is also called if pass is > 1,
 * which is a problem for FASTPASS because the file stack is empty.
 */
int GetCurrSrcPos( char *buffer )
/*******************************/
{
    struct src_item *curr;

    for( curr = src_stack; curr; curr = curr->next ) {
        if ( curr->type == SIT_FILE ) {
            return( sprintf( buffer, ModuleInfo.EndDirFound == FALSE ? "%s(%" I32_SPEC "u) : " : "%s : ", GetFName( curr->srcfile )->fname , curr->line_num ) );
        }
    }
    *buffer = NULLC;
    return( 0 );
}

/* for error listing, render the source nesting structure.
 * the structure consists of include files and macros.
 */

void print_source_nesting_structure( void )
/*****************************************/
{
    struct src_item *curr;
    unsigned        tab = 1;

    DebugMsg1(("print_source_nesting_structure enter, src_stack=%s\n",
              src_stack == NULL ? "<null>" : GetTopSrcName() ));
    /* in main source file? */
    if ( src_stack == NULL || src_stack->next == NULL )
        return;

    for( curr = src_stack; curr->next ; curr = curr->next ) {
        if( curr->type == SIT_FILE ) {
            PrintNote( NOTE_INCLUDED_BY, tab, "", GetFName( curr->srcfile )->fname, curr->line_num );
            tab++;
        } else {
            //char fname[_MAX_FNAME+_MAX_EXT];
            if (*(curr->mi->macro->name) == NULLC ) {
                PrintNote( NOTE_ITERATION_MACRO_CALLED_FROM, tab, "", "MacroLoop", curr->line_num, curr->mi->macro->value + 1 );
            } else {
                PrintNote( NOTE_MACRO_CALLED_FROM, tab, "", curr->mi->macro->name, curr->line_num, GetFNamePart( GetFName(((struct dsym *)curr->mi->macro)->e.macroinfo->srcfile)->fname ) ) ;
            }
            tab++;
        }
    }
    PrintNote( NOTE_MAIN_LINE_CODE, tab, "", GetFName( curr->srcfile )->fname, curr->line_num );
}

/* Scan the include path for a file!
 * variable ModuleInfo.g.IncludePath also contains directories set with -I cmdline option.
 */
static FILE *open_file_in_include_path( const char *name, char fullpath[] )
/*************************************************************************/
{
    char            *curr;
    char            *next;
    int             i;
    int             namelen;
    FILE            *file = NULL;

    while( isspace( *name ) )
        name++;

    curr = ModuleInfo.g.IncludePath;
    namelen = strlen( name );

    DebugMsg(("open_file_in_include_path(%s) enter\n", name ));
    for ( ; curr; curr = next ) {
        next = strchr( curr, INC_PATH_DELIM );
        if ( next ) {
            i = next - curr;
            next++; /* skip path delimiter char (; or :) */
        } else {
            i = strlen( curr );
        }

        /* v2.06: ignore
         * - "empty" entries in PATH
         * - entries which would cause a buffer overflow
         */
        if ( i == 0 || ( ( i + namelen ) >= FILENAME_MAX ) )
            continue;

        memcpy( fullpath, curr, i );
        if( fullpath[i-1] != '/'
#if !defined(__UNIX__)
           && fullpath[i-1] != '\\' && fullpath[i-1] != ':'
#endif
        ) {
            fullpath[i] = DIR_SEPARATOR;
            i++;
        }
        strcpy( fullpath+i, name );

        DebugMsg(("open_file_in_include_path: >%s<\n", fullpath ));
        file = fopen( fullpath, "rb" );
        if( file ) {
            break;
        }
    }
    DebugMsg(("open_file_in_include_path()=%p\n", file ));
    return( file );
}

/* the worker behind the INCLUDE directive. Also used
 * by INCBIN and the -Fi cmdline option.
 * the main source file is added in InputInit().
 * v2.12: _splitpath()/_makepath() removed
 */

FILE *SearchFile( const char *path, bool queue )
/**********************************************/
{
    FILE        *file = NULL;
    struct src_item *fl;
    const char  *fn;
    bool        isabs;
    char        fullpath[FILENAME_MAX];

    DebugMsg1(("SearchFile(%s) enter\n", path ));

    //_splitpath( path, drive, dir, fname, ext );
    //DebugMsg1(("SearchFile(): drive=%s, dir=%s, fname=%s, ext=%s\n", drive, dir, fname, ext ));
    fn = GetFNamePart( path );

    /* if no absolute path is given, then search in the directory
     * of the current source file first!
     * v2.11: various changes because field fullpath has been removed.
     */

    isabs = ISABS( path );
    //if ( dir[0] != '\\' && dir[0] != '/' ) {
    if ( !isabs ) {
        for ( fl = src_stack; fl ; fl = fl->next ) {
            if ( fl->type == SIT_FILE ) {
                const char  *fn2;
                char        *src;
                //_splitpath( GetFName( fl->srcfile )->fname, drive2, dir2, NULL, NULL );
                //DebugMsg1(("SearchFile(): curr src=%s, split into drive=%s, dir=%s\n", GetFName( fl->srcfile)->fname, drive2, dir2 ));
                src = GetFName( fl->srcfile )->fname;
                fn2 = GetFNamePart( src );
                if ( fn2 != src ) {
                    int i = fn2 - src;
                    /* v2.10: if there's a directory part, add it to the directory part of the current file.
                     * fixme: check that both parts won't exceed FILENAME_MAX!
                     * fixme: 'path' is relative, but it may contain a drive letter!
                     */
                    memcpy( fullpath, src, i );
                    strcpy( fullpath + i, path );
                    if ( file = fopen( fullpath, "rb" ) ) {
                        DebugMsg1(("SearchFile(): file found, fopen(%s)=%X\n", fullpath, file ));
                        path = fullpath;
                    }
#ifdef DEBUG_OUT
                    else
                        DebugMsg1(("SearchFile(): fopen(%s) failed\n", fullpath ));
#endif
                }
                break;
            }
        }
    }
    if ( file == NULL ) {
        fullpath[0] = NULLC;
        file = fopen( path, "rb" );
        DebugMsg1(("SearchFile(): fopen(%s)=%X\n", path, file ));

        /* if the file isn't found yet and include paths have been set,
         * and NO absolute path is given, then search include dirs
         */
        if( file == NULL && ModuleInfo.g.IncludePath != NULL && !isabs ) {
            if ( file = open_file_in_include_path( path, fullpath ) ) {
                DebugMsg1(("SearchFile(): open_file_in_include_path(%s)=%X [%s]\n", path, file, fullpath ));
                path = fullpath;
            }
#ifdef DEBUG_OUT
            else
                DebugMsg1(("SearchFile(): open_file_in_include_path(%s)=NULL\n", path ));
#endif
        }
        if( file == NULL ) {
            EmitErr( CANNOT_OPEN_FILE, path, ErrnoStr() );
            return( NULL );
        }
    }
    /* is the file to be added to the file stack?
     * assembly files usually are, but binary files ( INCBIN ) aren't.
     */
    if ( queue ) {
        fl = PushSrcItem( SIT_FILE, file );
        fl->srcfile = AddFile( path );
        FileCur->string_ptr = GetFName( fl->srcfile )->fname;
#if FILESEQ
        if ( Options.line_numbers && Parse_Pass == PASS_1 )
            AddFileSeq( fl->srcfile );
#endif
    }
    return( file );
}

/* get the next source line from file or macro.
 * v2.11: line queues are no longer read here,
 * this is now done in RunLineQueue().
 * Also, if EOF/EOM is reached, the function will
 * now return NULL in any case.
 */

char *GetTextLine( char *buffer )
/*******************************/
{
    struct src_item *curr = src_stack;

    if ( curr->type == SIT_FILE ) {

        if( my_fgets( buffer, MAX_LINE_LEN, curr->file ) ) {
            curr->line_num++;
#ifdef DEBUG_OUT
            if ( Parse_Pass == PASS_1 ) cntlines++;
#endif
            return( buffer );
        }
        DebugCmd( ModuleInfo.g.FNames[curr->srcfile].lines = curr->line_num );
        DebugMsg1(("GetTextLine: ***** EOF file %s (idx=%u) *****\n", GetFName( curr->srcfile )->fname, curr->srcfile ));
        /* don't close and remove main source file */
        if ( curr->next ) {
            fclose( curr->file );
            src_stack = curr->next;
            curr->next = SrcFree;
            SrcFree = curr;
        }
        /* update value of @FileCur variable */
        for( curr = src_stack; curr->type != SIT_FILE; curr = curr->next );
        FileCur->string_ptr = GetFName( curr->srcfile)->fname;
#if FILESEQ
        if ( Options.line_numbers && Parse_Pass == PASS_1 )
            AddFileSeq( curr->srcfile );
#endif

    } else {

        curr->mi->currline = ( curr->mi->currline ? curr->mi->currline->next : curr->mi->startline );
        if ( curr->mi->currline ) {
            /* if line contains placeholders, replace them by current values */
            if ( curr->mi->currline->ph_count ) {
                fill_placeholders( buffer,
                                  curr->mi->currline->line,
                                  curr->mi->parmcnt,
                                  curr->mi->localstart, curr->mi->parm_array );
            } else {
                strcpy( buffer, curr->mi->currline->line );
            }
            curr->line_num++;
#ifdef DEBUG_OUT
            if ( Parse_Pass == PASS_1 ) cntlines++;
#endif
            return( buffer );
        }
        src_stack = curr->next;
        curr->next = SrcFree;
        SrcFree = curr;
    }

    return( NULL ); /* end of file or macro reached */
}

/* add a string to the include path.
 * called for -I cmdline options.
 * the include path is rebuilt for each assembled module.
 * it is stored in the standard C heap.
 */
void AddStringToIncludePath( const char *string )
/***********************************************/
{
    char *tmp;
    int len;

    DebugMsg(("AddStringToIncludePath(%s) enter\n", string ));
    while( isspace( *string ) )
        string++;
    len = strlen( string );
    if ( len == 0 )
        return;
    if( ModuleInfo.g.IncludePath == NULL ) {
        ModuleInfo.g.IncludePath = MemAlloc( len + 1 );
        strcpy( ModuleInfo.g.IncludePath, string );
    } else {
        tmp = ModuleInfo.g.IncludePath;
        ModuleInfo.g.IncludePath = MemAlloc( strlen( tmp ) + sizeof( INC_PATH_DELIM_STR ) +
                                len + 1 );
        strcpy( ModuleInfo.g.IncludePath, tmp );
        strcat( ModuleInfo.g.IncludePath, INC_PATH_DELIM_STR );
        strcat( ModuleInfo.g.IncludePath, string );
        MemFree( tmp );
    }
}

#if 0
/* function to get value of @FileCur.
 * won't work, because text macros don't use asym.sfunc_ptr
 */
static void GetFileCur( struct asym *sym )
/****************************************/
{
    struct src_item *curr;

    for( curr = src_stack; curr && curr->type != SIT_FILE; curr = curr->next );
    sym->string_ptr = GetFName( curr->srcfile )->name;
    DebugMsg1(("GetFileCur: curr value=%s\n", sym->string_ptr ));
}
#endif

/* input buffers
 * 1. src line stack ( default I86: 2*600  = 1200 )
 * 2. tokenarray     ( default I86: 150*12 = 1800 )
 * 3. string buffer  ( default I86: 2*600  = 1200 )
 */

#ifdef __I86__
#define SIZE_SRCLINES     ( MAX_LINE_LEN * 2 )
#define SIZE_TOKENARRAY   ( sizeof( struct asm_tok ) * MAX_TOKEN )
#define SIZE_STRINGBUFFER ( MAX_LINE_LEN * 2 )
#else
#define SIZE_SRCLINES     ( MAX_LINE_LEN * ( MAX_MACRO_NESTING + 1 ) )
#define SIZE_TOKENARRAY   ( sizeof( struct asm_tok ) * MAX_TOKEN * MAX_MACRO_NESTING )
#define SIZE_STRINGBUFFER ( MAX_LINE_LEN * MAX_MACRO_NESTING )
#endif

/* PushInputStatus() is used whenever a macro or generated code is to be "executed".
 * after the macro/code has been assembled, PopInputStatus() is required to restore
 * the saved status.
 * the status information that is saved includes
 * - the source line ( including the comment )
 * - the token buffer
 * - the string buffer (used to store token strings)
 * - field Token_Count
 * - field line_flags
 */

struct asm_tok *PushInputStatus( struct input_status *oldstat )
/*************************************************************/
{
    oldstat->token_stringbuf = token_stringbuf;
    oldstat->token_count = Token_Count;
    oldstat->currsource = CurrSource;
    /* if there's a comment, attach it to current source */
    if ( ModuleInfo.CurrComment ) {
        int i = strlen( CurrSource );
        oldstat->CurrComment = CurrSource + i;
        strcpy( oldstat->CurrComment, ModuleInfo.CurrComment );
    } else
        oldstat->CurrComment = NULL;
    oldstat->line_flags = ModuleInfo.line_flags; /* v2.08 */
#ifdef __I86__
    oldstat->tokenarray = ModuleInfo.tokenarray;
    oldstat->stringbufferend = StringBufferEnd;
    CurrSource = MemAlloc( MAX_LINE_LEN + SIZE_TOKENARRAY + SIZE_STRINGBUFFER );
    ModuleInfo.tokenarray = (struct asm_tok *)( CurrSource + MAX_LINE_LEN );
    token_stringbuf = CurrSource + MAX_LINE_LEN + SIZE_TOKENARRAY;
#else
    token_stringbuf = StringBufferEnd;
    ModuleInfo.tokenarray += Token_Count + 1;
    CurrSource = GetAlignedPointer( CurrSource, strlen( CurrSource ) );
    /**/myassert( ( CurrSource + MAX_LINE_LEN ) <= (char *)ModuleInfo.tokenarray );
#ifdef DEBUG_OUT
    /**/myassert( ( ModuleInfo.tokenarray + sizeof( struct asm_tok ) * MAX_TOKEN ) <= end_tokenarray );
    /**/myassert( ( token_stringbuf + 2 * MAX_LINE_LEN ) <= end_stringbuf );
#endif
#endif
    DebugMsg1(("PushInputStatus() stringbuf-tokencnt-currsrc old=%X-%u-%X new=%X-%X-%X\n",
               oldstat->token_stringbuf, oldstat->token_count, oldstat->currsource,
               token_stringbuf, ModuleInfo.tokenarray, CurrSource ));
    return( ModuleInfo.tokenarray );
}

void PopInputStatus( struct input_status *newstat )
/*************************************************/
{
    DebugMsg1(("PopInputStatus() old=%X-%u-%X new=%X-%u-%X\n",
               token_stringbuf, Token_Count, CurrSource,
               newstat->token_stringbuf, newstat->token_count, newstat->currsource ));
#ifdef __I86__
    MemFree( CurrSource );
#else
    StringBufferEnd = token_stringbuf;
#endif
    token_stringbuf = newstat->token_stringbuf;
    Token_Count = newstat->token_count;
    CurrSource = newstat->currsource;
    if ( newstat->CurrComment ) {
        ModuleInfo.CurrComment = commentbuffer;
        strcpy( ModuleInfo.CurrComment, newstat->CurrComment );
        *newstat->CurrComment = NULLC;
    } else
        ModuleInfo.CurrComment = NULL;
#ifdef __I86__
    StringBufferEnd = newstat->stringbufferend;
    ModuleInfo.tokenarray = newstat->tokenarray;
#else
    ModuleInfo.tokenarray -= Token_Count + 1;
#endif
    ModuleInfo.line_flags = newstat->line_flags; /* v2.08 */
    return;
}

/* Initializer, called once for each module. */

void InputInit( void )
/********************/
{
    struct src_item *fl;
#if 0
    char        path[FILENAME_MAX];
    char        drive[_MAX_DRIVE];
    char        dir[_MAX_DIR];
#endif

    DebugMsg(( "InputInit() enter\n" ));
    //ModuleInfo.g.cnt_fnames = 0;
    //ModuleInfo.g.FNames = NULL;
    //IncludePath = NULL;
    //src_stack = NULL;
    SrcFree = NULL; /* v2.11 */
#if FILESEQ
    FileSeq.head = NULL;
#endif
#ifdef DEBUG_OUT
    cntppl0 = 0;
    cntppl1 = 0;
    cntppl2 = 0;
    cnttok0 = 0;
    cnttok1 = 0;
    cntflines = 0;
    cntlines = 0;
#endif

    /* add path of main module to the include path.
     * v2.12: unnecessary since v2.10, since the directory part of the
     * current source is added if a file is to be included; see SearchFile().
     */
    //_splitpath( CurrFName[ASM], drive, dir, NULL, NULL );
    //if ( drive[0] || dir[0] ) {
    //    _makepath( path, drive, dir, NULL, NULL );
    //    AddStringToIncludePath( path );
    //}

    srclinebuffer = LclAlloc( SIZE_SRCLINES + SIZE_TOKENARRAY + SIZE_STRINGBUFFER );
    /* the comment buffer is at the end of the source line buffer */
    commentbuffer = srclinebuffer + SIZE_SRCLINES - MAX_LINE_LEN;
    /* behind the comment buffer is the token buffer */
    ModuleInfo.tokenarray = (struct asm_tok *)( srclinebuffer + SIZE_SRCLINES );
    token_stringbuf = srclinebuffer + SIZE_SRCLINES + SIZE_TOKENARRAY;
#ifdef DEBUG_OUT
    end_tokenarray = (struct asm_tok *)token_stringbuf;
    end_stringbuf = token_stringbuf + SIZE_STRINGBUFFER;
    DebugMsg(( "InputInit: srclinebuffer=%p, tokenarray=%p, token_stringbuf=%p end_stringbuf=%p\n", srclinebuffer, ModuleInfo.tokenarray, token_stringbuf, end_stringbuf ));
#endif

    fl = PushSrcItem( SIT_FILE, CurrFile[ASM] );
    fl->srcfile = ModuleInfo.srcfile = AddFile( CurrFName[ASM] );
    /* setting a function pointer won't work for text macros! */
    //FileCur->sfunc_ptr = &GetFileCur;
    FileCur->string_ptr = GetFName( fl->srcfile )->fname;

    DebugMsg(( "InputInit() exit\n" ));
}

/* init for each pass */

void InputPassInit( void )
/************************/
{
    DebugMsg(( "InputPassInit() enter\n" ));
    src_stack->line_num = 0;
    //inside_comment = NULLC;
    CurrSource = srclinebuffer;
    *CurrSource = NULLC;
    StringBufferEnd = token_stringbuf;
#ifdef DEBUG_OUT
    Token_Count = 0;
#endif
    return;
}

/* release input buffers for a module */

void InputFini( void )
/********************/
{
#ifdef DEBUG_OUT
    int   i;

    /* for the main source file, lines usually isn't filled yet */
    if ( ModuleInfo.g.FNames )
        ModuleInfo.g.FNames[ModuleInfo.srcfile].lines = src_stack->line_num;
    for( i = 0; i < ModuleInfo.g.cnt_fnames; i++ ) {
        if ( Options.log_all_files ) {
            if ( ModuleInfo.g.FNames[i].included > 1 )
                printf("%2u: %5u *%2u %s\n", i+1, ModuleInfo.g.FNames[i].lines, ModuleInfo.g.FNames[i].included, ModuleInfo.g.FNames[i].fname );
            else
                printf("%2u: %5u     %s\n", i+1, ModuleInfo.g.FNames[i].lines, ModuleInfo.g.FNames[i].fname );
        }
        DebugMsg(( "InputFini: idx=%u name=%s\n", i, ModuleInfo.g.FNames[i].fname ));
    }
#endif
    if ( ModuleInfo.g.IncludePath )
        MemFree( ModuleInfo.g.IncludePath );

    /* free items in ModuleInfo.g.FNames ( and FreeFile, if FASTMEM==0 ) */
    FreeFiles();
#ifdef DEBUG_OUT
    if ( Options.quiet == FALSE ) {
        printf("lines read(files)/processed in pass one: %" I32_SPEC "u / %" I32_SPEC "u\n", cntflines, cntlines );
        printf("invokations: PreprocessLine=%" I32_SPEC "u/%" I32_SPEC "u/%" I32_SPEC "u, Tokenize=%" I32_SPEC "u/%" I32_SPEC "u\n", cntppl0, cntppl1, cntppl2, cnttok0, cnttok1 );
    }
#endif
    ModuleInfo.tokenarray = NULL;
#ifdef DEBUG_OUT
    Token_Count = 0; /* v2.19: this var is referenced in EmitErr()/EmitWarn() in debug version */
    token_stringbuf = NULL;
    StringBufferEnd = NULL;
    commentbuffer = NULL;
    CurrSource = NULL;
#endif
    LclFree( srclinebuffer );
}
