
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993 Microsoft Corporation.
*       All rights reserved. 
*       This source code is only intended as a supplement to 
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the 
*       Microsoft samples programs.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: rwinc.c
*
* Does the include file reading and writing.
*
* Functions:
*
*    OpenIncludeFile()
*    FreeInclude()
*    WriteInc()
*    LoadIncludeFile()
*    GetChar()
*    ReadChar()
*    GetLabel()
*    GetValue()
*    GetWord()
*    FindDefine()
*    GetNextInc()
*    RWToOffset()
*    WriteIncChar()
*    WriteIncFlush()
*    WriteChangedInc()
*    WriteDeletedInc()
*    WriteAddedInc()
*    WriteSymbol()
*    WriteIDInc()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

#include <ctype.h>


/*
 * Field width that the symbol is printed within.  This indirectly
 * determines where the id value starts, because blanks are added
 * after the symbol is printed up to this width value.
 */
#define CCHSYMFIELDWIDTH    27

/*
 * Return codes from the file reading functions.
 */
#define READ_OK             1
#define READ_EOF            2
#define READ_BAD            3
#define READ_WRONG          4
#define BAD_POINTER         ((VOID *)0xFFFF)

/*
 * Return codes from the GetNextInc function.
 */
#define GNI_DONE            0
#define GNI_NOCHANGE        1
#define GNI_CHANGED         2
#define GNI_DELETED         3
#define GNI_ADDED           4

static BYTE abBuffer[CCHFILEBUFFER];    /* Buffer for read file data.       */
static TCHAR achBuffer[CCHFILEBUFFER];  /* Unicode buffer for data.         */
static INT cbBuf;                       /* Pointer into achBuffer.          */
static DWORD cchFile;                   /* Count of characters read.        */
static DWORD cchFileMax;                /* Max characters in file.          */
static DWORD fposLastDefine;            /* Saves location of "#define".     */
static DWORD fposWordStart;             /* Saves start of id value.         */
static BOOL fAtNewLine;                 /* At start or \r or \n.            */
static HANDLE hfInclude;                /* The current include file.        */

STATICFN BOOL LoadIncludeFile(VOID);
STATICFN LPTSTR GetChar(VOID);
STATICFN LPTSTR ReadChar(VOID);
STATICFN INT GetLabel(BOOL *pfDups);
STATICFN INT GetValue(PINT pnValue);
STATICFN INT GetWord(LPTSTR pch);
STATICFN INT FindDefine(VOID);
STATICFN INT GetNextInc(NPLABEL *pplReturn, BOOL fFirst);
STATICFN BOOL RWToOffset(HANDLE hfWrite, DWORD lOffset);
STATICFN BOOL WriteIncChar(HANDLE hfWrite, TCHAR ch);
STATICFN BOOL WriteIncFlush(HANDLE hfWrite);
STATICFN BOOL WriteChangedInc(HANDLE hfWrite, NPLABEL plInc);
STATICFN BOOL WriteDeletedInc(HANDLE hfWrite, NPLABEL plInc);
STATICFN BOOL WriteAddedInc(HANDLE hfWrite, NPLABEL plInc);
STATICFN BOOL WriteSymbol(HANDLE hfWrite, LPTSTR pszSymbol);
STATICFN BOOL WriteIDInc(HANDLE hfWrite, INT id);



/****************************************************************************
* OpenIncludeFile
*
* This function attempts to open and load the include file with name
* pointed to by pszOpenInclude.  If pszOpenInclude is just a file name, and
* not a path, then the path is taken from szFullLoadFile.  Otherwise
* pszOpenInclude itself is used.  The full pathname is put in
* szFullIncludeFile and pszIncludeFile is set to point to just the file
* name in it.  
*
* If fDoOpen is TRUE, the file is opened.  If it is FALSE, it is assumed
* that hfInc contains the file handle to the opened include file and this
* handle is used.  In addition, the caller is responsible for closing
* any passed in file handle if an error occurs.
*
* Any existing includes are freed,  szFullIncludeFile is set to the full 
* include path, pszIncludeFile is set to the filename portion of this full 
* path and hfInclude will contain the file handle to the include file.
*
* Arguments:
*   LPTSTR pszOpenInclude - name of the include file to open.
*
* Returns:
*   If the load is successful, TRUE is returned.  Otherwise,
*   FALSE is returned.
*
****************************************************************************/

BOOL OpenIncludeFile(
    LPTSTR pszOpenInclude)
{
    TCHAR szFullIncludeFileTemp[CCHMAXPATH];
    HCURSOR hcurSave;
    BOOL fSuccess = FALSE;

    hcurSave = SetCursor(hcurWait);

    if (FileInPath(pszOpenInclude) == pszOpenInclude) {
        lstrcpy(szFullIncludeFileTemp, szFullResFile);
        lstrcpy(FileInPath(szFullIncludeFileTemp), pszOpenInclude);
    }
    else {
        lstrcpy(szFullIncludeFileTemp, pszOpenInclude);
    }

    /*
     * Close any existing include file and free memory.
     */
    FreeInclude();

    if ((hfInclude = CreateFile(szFullIncludeFileTemp, GENERIC_READ,
            FILE_SHARE_READ, NULL, OPEN_EXISTING,
            FILE_FLAG_SEQUENTIAL_SCAN, NULL)) != (HANDLE)-1) {
        if (LoadIncludeFile()) {
            lstrcpy(szFullIncludeFile, szFullIncludeFileTemp);
            pszIncludeFile = FileInPath(szFullIncludeFile);
            fSuccess = TRUE;
        }

        CloseHandle(hfInclude);
    }

    /*
     * Update the status windows symbol combo box.  Update other fields
     * also, in case the currently selected control's symbol was affected
     * by the reading of the new include file.
     */
    StatusFillSymbolList(plInclude);
    StatusUpdate();

    ShowFileStatus(TRUE);
    SetCursor(hcurSave);

    return fSuccess;
}



/************************************************************************
* LoadIncludeFile
*
* This function creates or adds to plInclude with all the #define
* statements in the file with handle hfInclude.
*
* Returns:
*     TRUE - Load succeeded.
*     FALSE - Load failed (a read error).
*
************************************************************************/

STATICFN BOOL LoadIncludeFile(VOID)
{
    INT RetCode;
    BOOL fDups = FALSE;

    /*
     * Set char count, get file cb.
     */
    cchFile = 0L;
    cchFileMax = GetFileSize((HANDLE)hfInclude, NULL);
    cbBuf = CCHFILEBUFFER;
    fAtNewLine = TRUE;

    /*
     * Loop through and extract all id definitions.
     */
    while ((RetCode = FindDefine()) != READ_EOF) {
        if (RetCode == READ_BAD || (RetCode = GetLabel(&fDups)) == READ_BAD) {
            Message(MSG_INTERNAL);
            return FALSE;
        }
    }

    /*
     * Warn the user if there were duplicate symbols,
     * or symbols with duplicate ids.
     */
    if (fDups)
        Message(MSG_IDUPIDS);

    return TRUE;
}



/****************************************************************************
* FindDefine
*
* This function looks for ^#define[\s\t], that "#define" at the start
* of a line and followed by a space or a tab.
*
* Returns:
*     READ_OK -> All OK & #define found.
*     READ_EOF -> All OK, but EOF found before #define.
*     READ_BAD = Failure on read.
*
****************************************************************************/

STATICFN INT FindDefine(VOID)
{
    LPTSTR pchIn;
    LPTSTR pchCmp;
    BOOL fLastAtNewLine;

tryagain:

    /*
     * Skip blank lines looking for a newline followed by a '#'.
     */
    while (TRUE) {
        fLastAtNewLine = fAtNewLine;
        pchIn = GetChar();

        if (pchIn == NULL)
            return READ_EOF;
        else if (pchIn == BAD_POINTER)
            return READ_BAD;
        else if (fLastAtNewLine && *pchIn == CHAR_POUND)
            break;
    }

    /*
     * At this point a newline followed by a '#' has been found.
     * Begin checking for "define".  Save away the file offset,
     * in case we have really found one.
     */
    fposLastDefine = cchFile - 1;
    pchCmp = ids(IDS_DEFINE);
    do {
        pchIn = GetChar();

        if (pchIn == BAD_POINTER)
            return READ_BAD;
        else if (pchIn == NULL || *pchIn != *pchCmp++)
            goto tryagain;
    } while (*pchCmp);

    /*
     * Finally, look for the trailing space or tab after the "#define".
     */
    pchIn = GetChar();
    if (pchIn == BAD_POINTER)
        return READ_BAD;
    else if (pchIn == NULL || (*pchIn != CHAR_SPACE && *pchIn != CHAR_TAB))
        goto tryagain;

    return READ_OK;
}



/************************************************************************
* GetLabel
*
* This function gets the next two words from the file hfInclude and treats
* them as a label and id, respectively.  It allocates another LABEL
* and string to hold this information.
*
* Arguments:
*   BOOL *pfDups = Points to a BOOL that will be set to TRUE if AddLabel
*                  finds a duplicate symbol or id.
*
* Returns:
*     READ_OK -> All OK.
*     READ_BAD = Failure on read.
*
************************************************************************/

STATICFN INT GetLabel(
    BOOL *pfDups)
{
    INT id;
    INT RetCode;
    TCHAR szLabel[CCHTEXTMAX];

    /*
     * Get string and ID at current position
     */
    switch (RetCode = GetWord(szLabel)) {
        case READ_OK:
            if ((RetCode = GetValue(&id)) == READ_OK) {
                AddLabel(szLabel, id, fposLastDefine,
                        (INT)(fposWordStart - fposLastDefine),
                        &plInclude, &plDelInclude, NULL, pfDups);
            }

            break;

        default:
            break;
    }

    return RetCode;
}



/************************************************************************
* GetWord
*
* This function uses GetChar to get the next word from the include
* file.  First it removes tabs and spaces, then it collects everything
* to the next white space.  Finally it null terminates the word.
*
* Arguments:
*     LPTSTR pch  - Where to put the word.
*
* Returns:
*     READ_OK - a word was found.
*     READ_EOF - EOF was found.
*     READ_BAD - Error on Read.
*     READ_WRONG - Found other than ' ' or '\t' followed by a letter,
*                   number or _, +, -.
*
************************************************************************/

STATICFN INT GetWord(
    LPTSTR pch)
{
    TCHAR ch;
    LPTSTR pchIn;

    /*
     * Skip spaces.
     */
    while ((pchIn = GetChar()) != NULL && pchIn != BAD_POINTER &&
                ((ch = *pchIn) == CHAR_SPACE || ch == CHAR_TAB))
        ;

    /*
     * Errors or EOF?
     */
    if (pchIn == NULL)
        return READ_EOF;
    else if (pchIn == BAD_POINTER)
        return READ_BAD;
    if (!iscsym(ch) && ch != CHAR_MINUS && ch != CHAR_PLUS)
        return READ_WRONG;

    /*
     * Save starting location of the word in the file.
     */
    fposWordStart = cchFile - 1;

    /*
     * Pick out the current word.
     */
    do {
        *pch++ = ch;
    } while ((pchIn = GetChar()) != NULL && pchIn != BAD_POINTER &&
            (ch = *pchIn) != CHAR_SPACE && ch != CHAR_TAB &&
            ch != CHAR_NEWLINE && ch != CHAR_RETURN);

    /*
     * Errors or EOF?
     */
    if (pchIn == NULL)
        return READ_WRONG;
    else if (pchIn == BAD_POINTER)
        return READ_BAD;

    /*
     * Null terminate the word.
     */
    *pch = (TCHAR)0;

    return READ_OK;
}



/************************************************************************
* GetChar
*
* This function returns a pointer to the next character in the
* stream hfInclude.  It calls ReadChar to do the actual work.
*
* As it is reading the stream, it will compress a comment sequence to
* a single space.  This means that from a slash+asterisk to the next
* asterisk+slash and from a pair of slashes to the end of the line all
* that will be returned is a single space character.
*
* Returns:
*     A pointer to next character in the stream hfInclude.
*     NULL => End of file.
*     BAD_POINTER => Problems reading file.
*
************************************************************************/

STATICFN LPTSTR GetChar(VOID)
{
    register LPTSTR pch;

    /*
     * Read the next character.
     */
    pch = ReadChar();
    if (pch == NULL || pch == BAD_POINTER)
        return pch;

    /*
     * Possibly starting a comment?
     */
    if (*pch == CHAR_SLASH) {
        /*
         * Starting a traditional comment?
         */
        if (*(pch + 1) == CHAR_ASTERISK) {
            /*
             * Read the '*'.
             */
            pch = ReadChar();
            if (pch == NULL || pch == BAD_POINTER)
                return pch;

            /*
             * Read until the next asterisk+slash is found.
             */
            do {
                pch = ReadChar();
                if (pch == NULL || pch == BAD_POINTER)
                    return pch;
            } while (*pch != CHAR_ASTERISK || *(pch + 1) != CHAR_SLASH);

            /*
             * Read the final '/'.
             */
            pch = ReadChar();
            if (pch == NULL || pch == BAD_POINTER)
                return pch;

            /*
             * Change it to a space.
             */
            *pch = CHAR_SPACE;
        }
        /*
         * Starting a single line comment?
         */
        else if (*(pch + 1) == CHAR_SLASH) {
            /*
             * Read up to the end of line.
             */
            do {
                pch = ReadChar();
                if (pch == NULL || pch == BAD_POINTER)
                    return pch;
            } while (*(pch + 1) != CHAR_RETURN && *(pch + 1) != CHAR_NEWLINE);

            /*
             * Convert the last character before the newline into a space.
             */
            *pch = CHAR_SPACE;
        }
    }

    return pch;
}



/************************************************************************
* ReadChar
*
* This function returns a pointer to the next character in the
* stream hfInclude, but does it in a buffered fashion.  That is, abBuffer
* is filled from hfInclude and pointers are returned to there.
* Note that after ReadChar is called, all previous pointers
* returned are meaningless.
*
* Returns:
*     A pointer to next character in the stream hfInclude.
*     NULL => End of file.
*     BAD_POINTER => Problems reading file.
*
* Comments:
*     May cause abBuffer to be filled from file with handle hfInclude.
*     cbBuf is changed.
*     cchFile is changed.
*     Sets fAtNewLine = TRUE if char returned is '\n' or '\r', or FALSE
*         otherwise.  Not changed unless a character is returned.
*
************************************************************************/

STATICFN LPTSTR ReadChar(VOID)
{
    register LPTSTR pch;
    INT cbRead;

    if (cchFile >= cchFileMax)
        return NULL;

    if (cbBuf >= CCHFILEBUFFER) {
        if ((cbRead = _lread((HFILE)hfInclude, abBuffer, CCHFILEBUFFER)) == -1)
            return BAD_POINTER;

        MultiByteToWideChar(CP_ACP, 0, abBuffer, cbRead, achBuffer,
                CCHFILEBUFFER);

        cbBuf = 0;
    }

    pch = achBuffer + cbBuf;
    cbBuf++;
    cchFile++;

    if (*pch == CHAR_DOSEOF) {
        cchFile = cchFileMax;
        return NULL;
    }

    fAtNewLine = (*pch == CHAR_RETURN || *pch == CHAR_NEWLINE) ? TRUE : FALSE;

    return pch;
}



/************************************************************************
* GetValue
*
* This function reads the next word in the file hfInclude with GetWord
* and converts that word to a number.
*
* If the second character of the word is an 'x' or 'X', the word is
* assumed to be a hex number and it is converted appropriately.
*
* Arguments:
*     npsValue - Where to put the value of the next word in file.
*
* Returns:
*     READ_OK - success.
*     READ_BAD - am error occured.
*     READ_WRONG - Something other than a number was found.
*
************************************************************************/

STATICFN INT GetValue(
    PINT pnValue)
{
    TCHAR achValue[CCHTEXTMAX];
    LPTSTR pch;
    INT RetValue;

    *pnValue = 0;
    if ((RetValue = GetWord(achValue)) != READ_OK)
        return RetValue;

    /*
     * Verify we have only a number.
     */
    pch = achValue;
    if (pch[1] == CHAR_CAP_X || pch[1] == CHAR_X) {
        if (*pch != CHAR_0) {
            RetValue = READ_WRONG;
        }
        else {
            for (pch += 2; *pch; pch++) {
                if (!iswxdigit(*pch)) {
                    RetValue = READ_WRONG;
                    break;
                }
            }

            if (RetValue == READ_OK)
                *pnValue = axtoi(&achValue[2]);
        }
    }
    else {
        if (!iswdigit(*pch) && *pch != CHAR_MINUS && *pch != CHAR_PLUS) {
            RetValue = READ_WRONG;
        }
        else {
            for (pch++; *pch; pch++) {
                if (!iswdigit(*pch)) {
                    RetValue = READ_WRONG;
                    break;
                }
            }

            if (RetValue == READ_OK)
                *pnValue = awtoi(achValue);
        }
    }

    return RetValue;
}



/************************************************************************
* FreeInclude
*
* This function frees the memory associated with an include file,
* sets global variables to match, and closes the currently open
* include file.  It frees plInclude, plDelInclude and all the LABELs in them.
* It sets gfIncChged to FALSE, sets pszIncludeFile to NULL, and
* closes any open include file.
*
************************************************************************/

VOID FreeInclude(VOID)
{
    FreeLabels(&plInclude);
    FreeLabels(&plDelInclude);
    gfIncChged = FALSE;
    pszIncludeFile = NULL;
}



/************************************************************************
* WriteInc
*
* This function writes the labels in plInclude to an include file.
*
* Arguments:
*   HANDLE hfWrite - handle to the file to write to.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
************************************************************************/

BOOL WriteInc(
    HANDLE hfWrite)
{
    INT nGNIRet;
    NPLABEL plInc;
    BOOL fEOF;

    /*
     * Is there an include file already specified?  If so,
     * open it.  If not, we are effectively at EOF now.
     */
    if (pszIncludeFile) {
        if ((hfInclude = CreateFile(szFullIncludeFile, GENERIC_READ,
                FILE_SHARE_READ, NULL, OPEN_EXISTING,
                FILE_FLAG_SEQUENTIAL_SCAN, NULL)) == (HANDLE)-1) {
            //if the include file is missing or locked...
            return FALSE;
        }

        fEOF = FALSE;
    }
    else {
        fEOF = TRUE;
    }

    cchFile = 0;
    cbWritePos = 0;
    cbBuf = CCHFILEBUFFER;
    fAtNewLine = TRUE;

    /*
     * Loop through all the includes.
     */
    nGNIRet = GetNextInc(&plInc, TRUE);
    while (nGNIRet != GNI_DONE) {
        switch (nGNIRet) {
            case GNI_NOCHANGE:
                break;

            case GNI_CHANGED:
                if (!WriteChangedInc(hfWrite, plInc))
                    return FALSE;

                break;

            case GNI_DELETED:
                if (!WriteDeletedInc(hfWrite, plInc))
                    return FALSE;

                break;

            case GNI_ADDED:
                /*
                 * The first time we reach an added label, we know that
                 * there are no more changed or deleted ones to handle
                 * so we read/write up to the end of the old include file.
                 * This only has to be done once.
                 */
                if (!fEOF) {
                    if (!RWToOffset(hfWrite, FPOS_MAX))
                        return FALSE;

                    fEOF = TRUE;

                    /*
                     * In the unlikely case that the read include file
                     * does not end with a carriage return and/or
                     * linefeed character, add them before beginning
                     * to write added labels.  This ensures that the
                     * first label added always starts on a new line.
                     */
                    if (!fAtNewLine) {
                        if (!WriteIncChar(hfWrite, CHAR_RETURN))
                            return FALSE;

                        if (!WriteIncChar(hfWrite, CHAR_NEWLINE))
                            return FALSE;
                    }
                }

                if (!WriteAddedInc(hfWrite, plInc))
                    return FALSE;

                break;
        }

        nGNIRet = GetNextInc(&plInc, FALSE);
    }

    /*
     * Write the rest of the file, if there is any left.
     */
    if (!fEOF)
        if (!RWToOffset(hfWrite, FPOS_MAX))
            return FALSE;

    /*
     * Flush any remaining characters in the write buffer.
     */
    if (!WriteIncFlush(hfWrite))
        return FALSE;

    /*
     * If we just opened the old include file, close it.
     */
    if (pszIncludeFile)
        CloseHandle(hfInclude);

    return TRUE;
}



/************************************************************************
* GetNextInc
*
* This routine will return the next label in the plInclude and plDelInclude
* linked lists, as well as the status of the returned label.
*
* The labels will be returned in order based upon their location in the
* lists, which is their order found in the include file if their fpos
* field is not FPOS_MAX.  This routine looks at the next label in both the
* plInclude and plDelInclude lists and returns the one with the lowest
* fpos.  Labels are returned from plInclude and plDelInclude in order of
* their fpos until all have been returned with a valid fpos.  After this,
* all new includes are returned from plInclude.
*
* Call it with fFirst equal to TRUE to initialize it.
*
* Arguments:
*   NPLABEL *pplReturn - label to return.
*   BOOL fFirst - TRUE if initializing.
*
* Returns:
*   GNI_DONE     - No more labels exist.
*   GNI_NOCHANGE - An existing label is being returned.
*   GNI_CHANGED  - An existing label with a changed id is being returned.
*   GNI_DELETED  - A deleted label is being returned.
*   GNI_ADDED    - An added label is being returned.
*
************************************************************************/

STATICFN INT GetNextInc(
    NPLABEL *pplReturn,
    BOOL fFirst)
{
    static NPLABEL plCur;
    static NPLABEL plDelCur;

    /*
     * Initialize if this is the first time.
     */
    if (fFirst) {
        plCur = plInclude;
        plDelCur = plDelInclude;
    }

    /*
     * Are we out of valid includes?
     */
    if (!plCur) {
        /*
         * If there are deleted ones left, return the next one.
         * Otherwise we are done.
         */
        if (plDelCur) {
            *pplReturn = plDelCur;
            plDelCur = plDelCur->npNext;
            return GNI_DELETED;
        }
        else {
            return GNI_DONE;
        }
    }
    /*
     * Have we reached the added includes (fpos == FPOS_MAX)?
     */
    else if (plCur->fpos == FPOS_MAX) {
        /*
         * If there are deleted ones remaining, return them first.
         * Otherwise, return the next added one.
         */
        if (plDelCur) {
            *pplReturn = plDelCur;
            plDelCur = plDelCur->npNext;
            return GNI_DELETED;
        }
        else {
            *pplReturn = plCur;
            plCur = plCur->npNext;
            return GNI_ADDED;
        }
    }
    else {
        /*
         * Return either the next label or the next deleted label,
         * based on whether there are any deleted labels and who
         * has the lowest file position (fpos).
         */
        if (plDelCur && plDelCur->fpos < plCur->fpos) {
            *pplReturn = plDelCur;
            plDelCur = plDelCur->npNext;
            return GNI_DELETED;
        }
        else {
            *pplReturn = plCur;
            plCur = plCur->npNext;
            /*
             * Return either GNI_CHANGE or GNI_NOCHANGE based on
             * whether the original id value has been changed.
             */
            return ((*pplReturn)->id == (*pplReturn)->idOrig) ?
                    GNI_NOCHANGE : GNI_CHANGED;
        }
    }
}



/************************************************************************
* RWToOffset
*
* This routine reads from the current include file and writes to the
* hfWrite file up to the lOffset position in the file.  If lOffset is
* set to FPOS_MAX, reads/writes are performed up to the end of the
* read file.
*
* Arguments:
*   HANDLE hfWrite - handle to the file to write to.
*   DWORD lOffset - where to write up to.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* Comments:
*   This routine relies on cchFile and cchFileMax to be properly updated
*   by the reading and writing routines.
*
************************************************************************/

STATICFN BOOL RWToOffset(
    HANDLE hfWrite,
    DWORD lOffset)
{
    LPTSTR pchIn;
    DWORD cbWrite;

    if (lOffset == FPOS_MAX)
        lOffset = cchFileMax;

    for (cbWrite = lOffset - cchFile; cbWrite; cbWrite--) {
        /*
         * NULL can be returned if there is an EOF character found in
         * the file.  This is not an error, and we will stop reading
         * and writing at this point.
         */
        if ((pchIn = ReadChar()) == NULL)
            return TRUE;

        /*
         * If BAD_POINTER is returned, there was an error reading the
         * include file.
         */
        if (pchIn == BAD_POINTER)
            return FALSE;

        /*
         * Write out the character.
         */
        if (!WriteIncChar(hfWrite, *pchIn))
            return FALSE;
    }

    return TRUE;
}



/************************************************************************
* WriteIncChar
*
* This routine writes a character (ch) to the hfWrite file, doing it in a
* buffered fashion.  Because it is buffered, before closing the file
* any remaining characters in the buffer must be "flushed" to disk.
*
* Arguments:
*   HANDLE hfWrite - handle to the file to write to.
*   TCHAR ch - character to write.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* Comments:
* The globals gachWriteBuffer and cbWritePos are updated by this routine.
*
************************************************************************/

STATICFN BOOL WriteIncChar(
    HANDLE hfWrite,
    TCHAR ch)
{
    INT cbWritten;

    gachWriteBuffer[cbWritePos++] = ch;

    /*
     * Is the buffer full?
     */
    if (cbWritePos == CCHFILEBUFFER) {
        CHAR abWriteBuffer[CCHFILEBUFFER];
        BOOL fDefCharUsed;

        WideCharToMultiByte(CP_ACP, 0, gachWriteBuffer, CCHFILEBUFFER,
                abWriteBuffer, CCHFILEBUFFER, NULL, &fDefCharUsed);

        cbWritten = (INT)_lwrite((HFILE)hfWrite, abWriteBuffer, cbWritePos);
        if (cbWritten != cbWritePos)
            return FALSE;

        cbWritePos = 0;
    }

    return TRUE;
}



/************************************************************************
* WriteIncFlush
*
* This routine flushes the write buffer.  This must be done before
* the file is closed or data can be lost.
*
* Arguments:
*   HANDLE hfWrite - handle to the file to write to.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* Comments:
*   The global cbWritePos is updated by this routine.
*
************************************************************************/

STATICFN BOOL WriteIncFlush(
    HANDLE hfWrite)
{
    INT cbWritten;

    /*
     * Are any bytes remaining in the buffer?
     */
    if (cbWritePos) {
        CHAR abWriteBuffer[CCHFILEBUFFER];
        BOOL fDefCharUsed;

        WideCharToMultiByte(CP_ACP, 0, gachWriteBuffer, cbWritePos,
                abWriteBuffer, CCHFILEBUFFER, NULL, &fDefCharUsed);

        cbWritten = (INT)_lwrite((HFILE)hfWrite, abWriteBuffer, cbWritePos);
        if (cbWritten != cbWritePos)
            return FALSE;

        cbWritePos = 0;
    }

    return TRUE;
}


 
/************************************************************************
* WriteChangedInc
*
* This routine writes out a label that has had its id changed since the
* include file was last read.
*
* Arguments:
*   HANDLE hfWrite - File to write to.
*   NPLABEL plInc  - Label to write.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* History:
*   03/13/90 Byron Dazey - Created.
************************************************************************/

STATICFN BOOL WriteChangedInc(
    HANDLE hfWrite,
    NPLABEL plInc)
{
    TCHAR ch;
    LPTSTR pchIn;

    if (!RWToOffset(hfWrite, plInc->fpos + plInc->nValueOffset))
        return FALSE;

    /*
     * Consume the old id value (up to the next space, tab,
     * beginning of a comment, newline or return).
     */
    while ((pchIn = ReadChar()) != NULL && pchIn != BAD_POINTER &&
            (ch = *pchIn) != CHAR_SPACE && ch != CHAR_TAB &&
            ch != CHAR_SLASH && ch != CHAR_NEWLINE && ch != CHAR_RETURN)
        ;

    /*
     * It is an error if ReadChar returns BAD_POINTER.  Note that it
     * is NOT an error if it reaches EOF (and returns NULL).
     */
    if (pchIn == BAD_POINTER)
        return FALSE;

    /*
     * Write the new one.
     */
    if (!WriteIDInc(hfWrite, plInc->id))
        return FALSE;

    /*
     * Remember to write the last character read after the old value.
     */
    if (pchIn != NULL)
        if (!WriteIncChar(hfWrite, *pchIn))
            return FALSE;

    return TRUE;
}



/************************************************************************
* WriteDeletedInc
*
* This routine deletes a label in the include file, closing up the
* space.  The entire line will be deleted, unless a comment is found
* after the id value.  If so, the comment and following characters will
* be left.
*
* Arguments:
*   HANDLE hfWrite - File to write to.
*   NPLABEL plInc  - Label to delete.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* History:
*   03/13/90 Byron Dazey - Created.
************************************************************************/

STATICFN BOOL WriteDeletedInc(
    HANDLE hfWrite,
    NPLABEL plInc)
{
    register INT i;
    TCHAR ch;
    LPTSTR pchIn;

    /*
     * Read and write up to the #define to be deleted.
     */
    if (!RWToOffset(hfWrite, plInc->fpos))
        return FALSE;

    /*
     * Consume up to the id value.
     */
    for (i = plInc->nValueOffset; i; i--)
        if ((pchIn = ReadChar()) == NULL || pchIn == BAD_POINTER)
            return FALSE;

    /*
     * Consume the id value and following characters up to the end of
     * the line or the beginning of a comment.
     */
    while ((pchIn = ReadChar()) != NULL && pchIn != BAD_POINTER &&
            (ch = *pchIn) != CHAR_NEWLINE && ch != CHAR_RETURN &&
            ch != CHAR_SLASH)
        ;

    if (pchIn == BAD_POINTER)
        return FALSE;

    /*
     * We are done if we have reached EOF.
     */
    if (pchIn == NULL)
        return TRUE;

    /*
     * If the beginning of a comment was found, be sure to write the
     * character back out and leave the rest of the comment.
     */
    if (ch == CHAR_SLASH) {
        if (!WriteIncChar(hfWrite, ch))
            return FALSE;
    }
    else {
        /*
         * At this point either a newline or a return was found
         * and we are going to consume it.  We also want to check
         * for a return following the newline, or a newline
         * following the return and consume it also.
         */
        if ((ch == CHAR_NEWLINE && *(pchIn + 1) == CHAR_RETURN) ||
                (ch == CHAR_RETURN && *(pchIn + 1) == CHAR_NEWLINE))
            if (ReadChar() == BAD_POINTER)
                return FALSE;
    }

    return TRUE;
}



/************************************************************************
* WriteAddedInc
*
* Adds a label to the new include file.
*
* Arguments:
*   HANDLE hfWrite - File to write to.
*   NPLABEL plInc  - Label to add.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* History:
*   03/13/90 Byron Dazey - Created.
************************************************************************/

STATICFN BOOL WriteAddedInc(
    HANDLE hfWrite,
    NPLABEL plInc)
{
    register LPTSTR psz;

    /*
     * Write the "#define " string.
     */
    psz = ids(IDS_POUNDDEFINE);
    while (*psz)
        if (!WriteIncChar(hfWrite, *psz++))
            return FALSE;

    /*
     * Write the symbol, followed by a space.
     */
    if (!WriteSymbol(hfWrite, plInc->pszLabel))
        return FALSE;
    if (!WriteIncChar(hfWrite, CHAR_SPACE))
        return FALSE;

    /*
     * Write the id, followed by a carriage return and newline.
     */
    if (!WriteIDInc(hfWrite, plInc->id))
        return FALSE;
    if (!WriteIncChar(hfWrite, CHAR_RETURN))
        return FALSE;
    if (!WriteIncChar(hfWrite, CHAR_NEWLINE))
        return FALSE;

    return TRUE;
}



/************************************************************************
* WriteSymbol
*
* Writes out a "#define DID_xxx  " string to hfWrite.  If the symbol
* is smaller than CCHSYMFIELDWIDTH, it will be padded with spaces out
* to this width.
*
* Arguments:
*   HANDLE hfWrite - handle to the file to write to.
*   LPTSTR pszSymbol - symbol to write.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
************************************************************************/

STATICFN BOOL WriteSymbol(
    HANDLE hfWrite,
    LPTSTR pszSymbol)
{
    register INT cch;

    /*
     * Write the symbol.
     */
    cch = 0;
    while (*pszSymbol) {
        if (!WriteIncChar(hfWrite, *pszSymbol++))
            return FALSE;

        cch++;
    }

    /*
     * Pad the field with blanks out to CCHSYMFIELDWIDTH, if necessary.
     */
    if (cch < CCHSYMFIELDWIDTH) {
        cch = CCHSYMFIELDWIDTH - cch;
        while (cch--)
            if (!WriteIncChar(hfWrite, CHAR_SPACE))
                return FALSE;
    }

    return TRUE;
}



/************************************************************************
* WriteIDInc
*
* Writes out an id value to the hfWrite file.  The format will be in
* either hex or decimal, depending on the current mode.
*
* Arguments:
*   HANDLE hfWrite - File to write to.
*   INT id         - ID to write.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
************************************************************************/

STATICFN BOOL WriteIDInc(
    HANDLE hfWrite,
    INT id)
{
    register LPTSTR psz;
    TCHAR szValue[CCHIDMAX + 1];

    Myitoa(id, szValue);

    psz = szValue;
    while (*psz)
        if (!WriteIncChar(hfWrite, *psz++))
            return FALSE;

    return TRUE;
}
