/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#define DEFAULT_MANIFEST_EXT ".mn"
#define DEFAULT_MAKEFILE_EXT ".win"

typedef struct char_list_struct {
    char *m_pString;
    struct char_list_struct *m_pNext;
} char_list;

typedef struct macro_list_struct   {
    char *m_pMacro;
    char_list *m_pValue;
    struct macro_list_struct *m_pNext;
} macro_list;

void help(void);
char *input_filename(const char *);
char *output_filename(const char *, const char *);
int input_to_output(FILE *, FILE *);
int output_rules(FILE *);
int output_end(FILE *);
int buffer_to_output(char *, FILE *);
macro_list *extract_macros(char *);
char *find_macro(char *, char **);
void add_macro(char *, macro_list **);
int macro_length(char *);
int value_length(char *);
void add_values(char *, char_list **);
char *skip_white(char *);
int write_macros(macro_list *, FILE *);
int write_values(char_list *, FILE *, int);
void free_macro_list(macro_list *);
void free_char_list(char_list *);
void morph_macro(macro_list **, char *, char *, char *);
void slash_convert(macro_list *, char *);
int explicit_rules(macro_list *, char *, FILE *);
void create_classroot(macro_list **ppList );

int main(int argc, char *argv[])
{
	int iOS = 0;
	char *pInputFile = NULL;
	char *pOutputFile = NULL;

	/*      Figure out arguments.
	 *      [REQUIRED] First argument is input file.
	 *      [OPTIONAL] Second argument is output file.
	 */
	if(argc > 1)    {
		FILE *pInputStream = NULL;
		FILE *pOutputStream = NULL;

		/*      Form respective filenames.
		 */
		pInputFile = input_filename(argv[1]);
		pOutputFile = output_filename(pInputFile, argc > 2 ? argv[2] : NULL);

		if(pInputFile == NULL)  {
			fprintf(stderr, "MANTOMAK:  Unable to form input filename\n");
			iOS = 1;
		}
		else    {
			pInputStream = fopen(pInputFile, "rb");
			if(pInputStream == NULL)        {
				fprintf(stderr, "MANTOMAK:  Unable to open input file %s\n", pInputFile);
				iOS = 1;
			}
		}
		if(pOutputFile == NULL) {
			fprintf(stderr, "MANTOMAK:  Unable to form output filename\n");
			iOS = 1;
		}
		else if(pInputStream != NULL)   {
			pOutputStream = fopen(pOutputFile, "wt");
			if(pOutputStream == NULL)       {
				fprintf(stderr, "MANTOMAK:  Unable to open output file %s\n", pOutputFile);
				iOS = 1;
			}
		}

		/*      Only do the real processing if our error code is not
		 *              already set.
		 */
		if(iOS == 0)    {
			iOS = input_to_output(pInputStream, pOutputStream);
		}

		if(pInputStream != NULL)        {
			fclose(pInputStream);
			pInputStream = NULL;
		}
		if(pOutputStream != NULL)       {
			fclose(pOutputStream);
			pOutputStream = NULL;
		}
	}
	else    {
		help();
		iOS = 1;
	}

	if(pInputFile)  {
		free(pInputFile);
		pInputFile = NULL;
	}
	if(pOutputFile) {
		free(pOutputFile);
		pOutputFile = NULL;
	}

	return(iOS);
}

void help(void)
{
	fprintf(stderr, "USAGE:\tmantomak.exe InputFile [OutputFile]\n\n");
	fprintf(stderr, "InputFile:\tManifest file.  If without extension, \"%s\" assumed.\n", DEFAULT_MANIFEST_EXT);
	fprintf(stderr, "OutputFile:\tNMake file.  If not present, \"InputFile%s\" assumed.\n", DEFAULT_MAKEFILE_EXT);
}

char *input_filename(const char *pInputFile)
{
	char aResult[_MAX_PATH];
	char aDrive[_MAX_DRIVE];
	char aDir[_MAX_DIR];
	char aName[_MAX_FNAME];
	char aExt[_MAX_EXT];

	if(pInputFile == NULL)  {
		return(NULL);
	}

	_splitpath(pInputFile, aDrive, aDir, aName, aExt);

	if(aExt[0] == '\0')     {
		/*      No extension provided.
		 *      Use the default.
		 */
		strcpy(aExt, DEFAULT_MANIFEST_EXT);
	}

	aResult[0] = '\0';
	_makepath(aResult, aDrive, aDir, aName, aExt);

	if(aResult[0] == '\0')  {
		return(NULL);
	}
	else    {
		return(strdup(aResult));
	}
}

char *output_filename(const char *pInputFile, const char *pOutputFile)
{
	char aResult[_MAX_PATH];
	char aDrive[_MAX_DRIVE];
	char aDir[_MAX_DIR];
	char aName[_MAX_FNAME];
	char aExt[_MAX_EXT];

	if(pOutputFile != NULL) {
		return(strdup(pOutputFile));
	}

	/*      From here on out, we have to create our own filename,
	 *              implied from the input file name.
	 */

	if(pInputFile == NULL)  {
		return(NULL);
	}

	_splitpath(pInputFile, aDrive, aDir, aName, aExt);
	strcpy(aExt, DEFAULT_MAKEFILE_EXT);

	aResult[0] = '\0';
	_makepath(aResult, aDrive, aDir, aName, aExt);

	if(aResult[0] == '\0')  {
		return(NULL);
	}
	else    {
		return(strdup(aResult));
	}
}

int input_to_output(FILE *pInput, FILE *pOutput)
{
	char *pHog = NULL;
	long lSize = 0;
	int iRetval = 0;

	/*      Read the entire file into memory.
	 */
	fseek(pInput, 0, SEEK_END);
	lSize = ftell(pInput);
	fseek(pInput, 0, SEEK_SET);

	pHog = (char *)malloc(lSize + 1);
	if(pHog)        {
		*(pHog + lSize) = '\0';
		fread(pHog, lSize, 1, pInput);

		iRetval = buffer_to_output(pHog, pOutput);

		free(pHog);
		pHog = NULL;
	}
	else    {
		fprintf(stderr, "MANTOMAK:  Out of Memory....\n");
		iRetval = 1;
	}

	return(iRetval);
}

int output_rules(FILE *pOutput)
{
	int iRetval = 0;

	if(EOF ==
	fputs("\n"
	      "!if \"$(MANIFEST_LEVEL)\"==\"RULES\""
	      "\n",
	    pOutput))
    {
		fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
		iRetval = 1;
	}
	return(iRetval);
}

int output_end(FILE *pOutput)
{
	int iRetval = 0;

	if(EOF ==
	fputs("\n"
	      "!endif"
	      "\n",
	    pOutput))
    {
		fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
		iRetval = 1;
	}
	return(iRetval);
}


int buffer_to_output(char *pBuffer, FILE *pOutput)
{
    int iRetval = 0;
    macro_list *pMacros = NULL;

    /*  Tokenize the macros and their corresponding values.
     */
    pMacros = extract_macros(pBuffer);
    if(pMacros != NULL) {
	/*  Perform forward to backslash conversion on those macros known to be
	 *      path information only.
	 */
	slash_convert(pMacros, "JBOOTDIRS");
	slash_convert(pMacros, "JDIRS");
	slash_convert(pMacros, "DEPTH");
	slash_convert(pMacros, "NS_DEPTH");
	slash_convert(pMacros, "PACKAGE");
	slash_convert(pMacros, "JMC_GEN_DIR");
	slash_convert(pMacros, "DIST_PUBLIC");

	/*  Process some of the macros, and convert them
	 *      into different macros with different data.
	 */
	morph_macro(&pMacros, "JMC_GEN", "JMC_HEADERS", "$(JMC_GEN_DIR)\\%s.h");
	morph_macro(&pMacros, "JMC_GEN", "JMC_STUBS", "$(JMC_GEN_DIR)\\%s.c");
	morph_macro(&pMacros, "JMC_GEN", "JMC_OBJS", ".\\$(OBJDIR)\\%s.obj");
	morph_macro(&pMacros, "CSRCS", "C_OBJS", ".\\$(OBJDIR)\\%s.obj");
	morph_macro(&pMacros, "CPPSRCS", "CPP_OBJS", ".\\$(OBJDIR)\\%s.obj");
	morph_macro(&pMacros, "REQUIRES", "LINCS", "-I$(XPDIST)\\public\\%s");

	create_classroot( &pMacros );

	/*  Output the Macros and the corresponding values.
	 */
	iRetval = write_macros(pMacros, pOutput);

	/*  Output rule file inclusion
	 */
	if(iRetval == 0)    {
	    iRetval = output_rules(pOutput);
	}

	/*  Output explicit build rules/dependencies for JMC_GEN.
	 */
	if(iRetval == 0)    {
	    iRetval = explicit_rules(pMacros, "JMC_GEN", pOutput);
	}

	if(iRetval == 0)    {
	    iRetval = output_end(pOutput);
	}
	/*  Free off the macro list.
	 */
	free_macro_list(pMacros);
	pMacros = NULL;
    }

    return(iRetval);
}

int explicit_rules(macro_list *pList, char *pMacro, FILE *pOutput)
{
    int iRetval = 0;
    macro_list *pEntry = NULL;

    if(pList == NULL || pMacro == NULL || pOutput == NULL) {
	return(0);
    }

    /*  Find macro of said name.
     *  Case insensitive.
     */
    pEntry = pList;
    while(pEntry)    {
	if(stricmp(pEntry->m_pMacro, pMacro) == 0)  {
	    break;
	}

	pEntry = pEntry->m_pNext;
    }

    if(pEntry)  {
	/*  Decide style of rule depending on macro name.
	 */
	if(stricmp(pEntry->m_pMacro, "JMC_GEN") == 0)    {
	    char_list *pNames = NULL;
	    char *pModuleName = NULL;
	    char *pClassName = NULL;

	    pNames = pEntry->m_pValue;
	    while(pNames)   {
		pModuleName = pNames->m_pString;
		pClassName = pModuleName + 1;

		fprintf(pOutput, "$(JMC_GEN_DIR)\\%s.h", pModuleName);
		fprintf(pOutput, ": ");
		fprintf(pOutput, "$(JMCSRCDIR)\\%s.class", pClassName);
		fprintf(pOutput, "\n    ");
		fprintf(pOutput, "$(JMC) -d $(JMC_GEN_DIR) -interface $(JMC_GEN_FLAGS) $(?F:.class=)");
		fprintf(pOutput, "\n");

		fprintf(pOutput, "$(JMC_GEN_DIR)\\%s.c", pModuleName);
		fprintf(pOutput, ": ");
		fprintf(pOutput, "$(JMCSRCDIR)\\%s.class", pClassName);
		fprintf(pOutput, "\n    ");
		fprintf(pOutput, "$(JMC) -d $(JMC_GEN_DIR) -module $(JMC_GEN_FLAGS) $(?F:.class=)");
		fprintf(pOutput, "\n");

		pNames = pNames->m_pNext;
	    }
	}
	else    {
	    /*  Don't know how to format macro.
	     */
	    iRetval = 69;
	}
    }

    return(iRetval);
}

void slash_convert(macro_list *pList, char *pMacro)
{
    macro_list *pEntry = NULL;

    if(pList == NULL || pMacro == NULL) {
	return;
    }

    /*  Find macro of said name.
     *  Case insensitive.
     */
    pEntry = pList;
    while(pEntry)    {
	if(stricmp(pEntry->m_pMacro, pMacro) == 0)  {
	    break;
	}

	pEntry = pEntry->m_pNext;
    }

    if(pEntry)  {
	char *pConvert = NULL;
	char_list *pValue = pEntry->m_pValue;

	while(pValue)   {
	    pConvert = pValue->m_pString;
	    while(pConvert && *pConvert)    {
		if(*pConvert == '/')    {
		    *pConvert = '\\';
		}
		pConvert++;
	    }
	    pValue = pValue->m_pNext;
	}
    }
}

void morph_macro(macro_list **ppList, char *pMacro, char *pMorph, char *pPrintf)
{
    macro_list *pEntry = NULL;

    if(ppList == NULL || pMacro == NULL || pMorph == NULL || pPrintf == NULL) {
	return;
    }

    /*  Find macro of said name.
     *  Case insensitive.
     */
    pEntry = *ppList;
    while(pEntry)    {
	if(stricmp(pEntry->m_pMacro, pMacro) == 0)  {
	    break;
	}

	pEntry = pEntry->m_pNext;
    }

    if(pEntry)  {
	char_list *pFilename = NULL;
	char aPath[_MAX_PATH];
	char aDrive[_MAX_DRIVE];
	char aDir[_MAX_DIR];
	char aFName[_MAX_FNAME];
	char aExt[_MAX_EXT];
	char *pBuffer = NULL;

	/*  Start with buffer size needed.
	 *  We expand this as we go along if needed.
	 */
	pBuffer = (char *)malloc(strlen(pMorph) + 2);
	strcpy(pBuffer, pMorph);
	strcat(pBuffer, "=");

	/*  Go through each value, converting over to new macro.
	 */
	pFilename = pEntry->m_pValue;
	while(pFilename) {
	    _splitpath(pFilename->m_pString, aDrive, aDir, aFName, aExt);

	    /*  Expand buffer by required amount.
	     */
	    sprintf(aPath, pPrintf, aFName);
	    strcat(aPath, " ");
	    pBuffer = (char *)realloc(pBuffer, _msize(pBuffer) + strlen(aPath));
	    strcat(pBuffer, aPath);

	    pFilename = pFilename->m_pNext;
	}

	/*  Add the macro.
	 */
	add_macro(pBuffer, ppList);

	free(pBuffer);
	pBuffer = NULL;
    }
}

void create_classroot(macro_list **ppList )
{
    char cwd[512];
    int i, i2;
    macro_list *pEntry = NULL;
    macro_list *pE;

    /*  Find macro of said name.
     *  Case insensitive.
     */
    pEntry = *ppList;
    while(pEntry)    {
	if(stricmp(pEntry->m_pMacro, "PACKAGE") == 0)  {
	    break;
	}

	pEntry = pEntry->m_pNext;
    }

    if(pEntry == 0 || pEntry->m_pValue == 0 || pEntry->m_pValue->m_pString == 0)  {
	return;
    }

    _getcwd( cwd, 512 );

    i = strlen( pEntry->m_pValue->m_pString );
    i2 = strlen( cwd );

    cwd[i2-i-1] = 0;

    pE = NULL;
    pE = (macro_list *)calloc(sizeof(macro_list),1);
    pE->m_pMacro = strdup("CLASSROOT");
    pE->m_pValue = (char_list *)calloc(sizeof(char_list),1);
    pE->m_pValue->m_pString = strdup(cwd);

    while(*ppList)  {
	ppList = &((*ppList)->m_pNext);
    }
    *ppList = pE;
}


int write_macros(macro_list *pList, FILE *pOutput)
{
    int iRetval = 0;
    int iLineLength = 0;

    if(pList == NULL || pOutput == NULL)    {
	return(0);
    }

	if(EOF ==
	fputs("\n"
	      "!if \"$(MANIFEST_LEVEL)\"==\"MACROS\""
	      "\n",
	    pOutput))
    {
		fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
		return(1);
	}

    while(pList)    {
        int bIgnoreForWin16 = 0;

        /* The following macros should not be emitted for Win16 */
        if (0 == strcmp(pList->m_pMacro, "LINCS")) {
            bIgnoreForWin16 = 1;
        }


        if (bIgnoreForWin16) {
            if(0 > fprintf(pOutput, "!if \"$(MOZ_BITS)\" != \"16\"\n"))  {
                fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
                iRetval = 1;
                break;
            }
        }

	if(0 > fprintf(pOutput, "%s=", pList->m_pMacro))  {
		fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
	    iRetval = 1;
	    break;
	}
	iLineLength += strlen(pList->m_pMacro) + 1;

	iRetval = write_values(pList->m_pValue, pOutput, iLineLength);
	if(iRetval) {
	    break;
	}

	if(EOF == fputc('\n', pOutput))    {
		fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
	    iRetval = 1;
	    break;
	}
	iLineLength = 0;

	pList = pList->m_pNext;

        if (bIgnoreForWin16) {
            if(0 > fprintf(pOutput, "!endif\n"))  {
                fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
                iRetval = 1;
                break;
            }
            bIgnoreForWin16 = 0;
        }
    }

	if(EOF ==
	fputs("\n"
	      "!endif"
	      "\n",
	    pOutput))
    {
		fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
		return(1);
	}
    return(iRetval);
}

int write_values(char_list *pList, FILE *pOutput, int iLineLength)
{
    int iRetval = 0;
    
    if(pList == NULL || pOutput == NULL)    {
	return(0);
    }

    while(pList)    {
	if(iLineLength == 0)    {
	    if(EOF == fputs("    ", pOutput))  {
		    fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
		iRetval = 1;
		break;
	    }
	    iLineLength += 4;

	    if(0 > fprintf(pOutput, "%s ", pList->m_pString))   {
		    fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
		iRetval = 1;
		break;
	    }
	    iLineLength += strlen(pList->m_pString) + 1;
	}
	else if(iLineLength + strlen(pList->m_pString) > 72)    {
	    if(EOF == fputs("\\\n", pOutput)) {
		    fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
		iRetval = 1;
		break;
	    }
	    iLineLength = 0;
	    continue;
	}
	else    {
	    if(0 > fprintf(pOutput, "%s ", pList->m_pString))   {
		    fprintf(stderr, "MANTOMAK:  Error writing to file....\n");
		iRetval = 1;
		break;
	    }
	    iLineLength += strlen(pList->m_pString) + 1;
	}

	pList = pList->m_pNext;
    }

    return(iRetval);
}

macro_list *extract_macros(char *pBuffer)
{
    macro_list *pRetval = NULL;
    char *pTraverse = NULL;
    char *pMacro = NULL;

    pTraverse = pBuffer;
    while(pTraverse)    {
	pMacro = NULL;
	pTraverse = find_macro(pTraverse, &pMacro);
	if(pMacro)  {
	    add_macro(pMacro, &pRetval);
	}
    }

    return(pRetval);
}

void add_macro(char *pString, macro_list **ppList)
{
    macro_list *pEntry = NULL;
    int iLength = 0;

    if(pString == NULL || *pString == '\0' || ppList == NULL) {
	return;
    }

    /*  Allocate a new list entry for the macro.
     */
    pEntry = (macro_list *)calloc(1, sizeof(macro_list));

    /*  Very first part of the string is the macro name.
     *  How long is it?
     */
    iLength = macro_length(pString);
    pEntry->m_pMacro = (char *)calloc(iLength + 1, 1);
    strncpy(pEntry->m_pMacro, pString, iLength);

    /*  Skip to the values.
     *  These are always on the right side of an '='
     */
    pString = strchr(pString, '=');
    if(pString) {
	pString++;
    }
    add_values(pString, &(pEntry->m_pValue));

    /*  Add the macro to the end of the macro list.
     */
    while(*ppList)  {
	ppList = &((*ppList)->m_pNext);
    }
    *ppList = pEntry;
}

void add_values(char *pString, char_list **ppList)
{
    char_list **ppTraverse = NULL;
    char_list *pEntry = NULL;
    int iLength = 0;
    int iBackslash = 0;

    if(pString == NULL || *pString == '\0' || ppList == NULL)   {
	return;
    }

    while(pString)  {
	/*  Find start of value.
	 */
	iBackslash = 0;
	while(*pString) {
	    if(*pString == '\\')    {
		iBackslash++;
	    }
	    else if(*pString == '\n')   {
		if(iBackslash == 0)  {
		    /*  End of values.
		     *  Setting to NULL gets out of all loops.
		     */
		    pString = NULL;
		    break;
		}
		iBackslash = 0;
	    }
	    else if(!isspace(*pString))  {
		/*  Backslashes part of string.
		 *  This screws up if a backslash is in the middle of the string.
		 */
		pString -= iBackslash;
		break;
	    }

	    pString++;
	}
	if(pString == NULL || *pString == '\0') {
	    break;
	}

	/*  Do not honor anything beginning with a #
	 */
	if(*pString == '#') {
	    /*  End of line.
	     */
	    while(*pString && *pString != '\n') {
		pString++;
	    }
	    continue;
	}

	/*  Very first part of the string is value name.
	 *  How long is it?
	 */
	iLength = value_length(pString);

	/*  Do not honor $(NULL)
	 */
	if(_strnicmp(pString, "$(NULL)", 7) == 0)    {
	    pString += iLength;
	    continue;
	}

	/*  Allocate a new list entry for the next value.
	 */
	pEntry = (char_list *)calloc(1, sizeof(char_list));

	pEntry->m_pString = (char *)calloc(iLength + 1, 1);
	strncpy(pEntry->m_pString, pString, iLength);

	/*  Add new value entry to the end of the list.
	 */
	ppTraverse = ppList;
	while(*ppTraverse)  {
	    ppTraverse = &((*ppTraverse)->m_pNext);
	}
	*ppTraverse = pEntry;

	/*  Go on to next value.
	 */
	pString += iLength;
    }
}

char *find_macro(char *pBuffer, char **ppMacro)
{
    char *pRetval = NULL;
    int iBackslash = 0;

    if(pBuffer == NULL || ppMacro == NULL) {
	return(NULL);
    }

    /*  Skip any whitespace in the buffer.
     *  If comments need to be skipped also, this is the place.
     */
    while(1)    {
	while(*pBuffer && isspace(*pBuffer))    {
	    pBuffer++;
	}
	if(*pBuffer == '#') {
	    /*  Go to the end of the line, it's a comment.
	     */
	    while(*pBuffer && *pBuffer != '\n') {
		pBuffer++;
	    }

	    continue;
	}
	break;
    }

    if(*pBuffer)    {
	/*  Should be at the start of a macro.
	 */
	*ppMacro = pBuffer;
    }

    /*  Find the end of the macro for the return value.
     *  This is the end of a line which does not contain a backslash at the end.
     */
    while(*pBuffer) {
	if(*pBuffer == '\\')    {
	    iBackslash++;
	}
	else if(*pBuffer == '\n')   {
	    if(iBackslash == 0)  {
		pRetval = pBuffer + 1;
		break;
	    }
	    iBackslash = 0;
	}
	else if(!isspace(*pBuffer))  {
	    iBackslash = 0;
	}

	pBuffer++;
    }

    return(pRetval);
}

int macro_length(char *pMacro)
{
    int iRetval = 0;

    if(pMacro == NULL)  {
	return(0);
    }

    /*  Length is no big deal.
     *  Problem is finding the end:
     *      whitespace
     *      '='
     */
    while(*pMacro)  {
	if(*pMacro == '=')  {
	    break;
	}
	else if(isspace(*pMacro))   {
	    break;
	}
	
	pMacro++;
	iRetval++;
    }

    return(iRetval);
}

int value_length(char *pValue)
{
    int iRetval = 0;

    if(pValue == NULL)  {
	return(0);
    }

    /*  Length is no big deal.
     *  Problem is finding the end:
     *      whitespace
     *      '\\'whitespace
     */
    while(*pValue)  {
	if(*pValue == '\\')  {
	    char *pFindNewline = pValue + 1;
	    /*  If whitespace to end of line, break here.
	     */
	    while(isspace(*pFindNewline))   {
		if(*pFindNewline == '\n')   {
		    break;
		}
		pFindNewline++;
	    }
	    if(*pFindNewline == '\n')   {
		break;
	    }
	}
	else if(isspace(*pValue))   {
	    break;
	}
	
	pValue++;
	iRetval++;
    }

    return(iRetval);
}

char *skip_white(char *pString)
{
    if(pString == NULL) {
	return(NULL);
    }

    while(*pString && isspace(*pString)) {
	pString++;
    }

    return(pString);
}

void free_macro_list(macro_list *pList)
{
    macro_list *pFree = NULL;

    if(pList == NULL)   {
	return;
    }

    while(pList)    {
	pFree = pList;
	pList = pList->m_pNext;

	pFree->m_pNext = NULL;

	free_char_list(pFree->m_pValue);
	pFree->m_pValue = NULL;

	free(pFree->m_pMacro);
	pFree->m_pMacro = NULL;

	free(pFree);
	pFree = NULL;
    }
}

void free_char_list(char_list *pList)
{
    char_list *pFree = NULL;

    if(pList == NULL)   {
	return;
    }

    while(pList)    {
	pFree = pList;
	pList = pList->m_pNext;

	pFree->m_pNext = NULL;

	free(pFree->m_pString);
	pFree->m_pString = NULL;

	free(pFree);
	pFree = NULL;
    }
}
