/*
 * Configurable ps-like program.
 * Macro definition routines.
 *
 * Copyright (c) 2010 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 */

#include "ips.h"


/*
 * Number of different macro lists.
 */
#define	MACRO_TYPE_COUNT	3


/*
 * Macro definition.
 * This is a variable sized structure, with all its string data allocated
 * within it.  So the structure must be treated as a unit, and its elements
 * cannot be individually freed.  Only the complete structure can be freed.
 */
typedef	struct MACRO MACRO;

struct MACRO
{
	MACRO *	next;			/* next macro in list */
	char *	name;			/* macro name */
	char **	values;			/* replacement name list */
	int	count;			/* number of elements in list */
	char	buf[1];			/* string buffer (variable length) */
};

#define	NULL_MACRO	((MACRO *) 0)


/*
 * Header for macro lists.
 */
typedef	struct
{
	char *	define;		/* string used in configuration files */
	BOOL	words;		/* whether macro is expanded by words */
	MACRO *	list;		/* list of macros */
} MACRO_HEADER;


/*
 * The headers for all type of macros.
 * These definitions cannot be changed in isolation.
 */
static	MACRO_HEADER	macroLists[MACRO_TYPE_COUNT] =
{
	{"option",	TRUE,	NULL},
	{"column",	TRUE,	NULL},
	{"expr",	FALSE,	NULL}
};


/*
 * Local procedures.
 */
static	MACRO_HEADER *	FindMacroHeader(MACRO_TYPE type);
static	MACRO *		FindMacro(MACRO_HEADER * head, const char * name);


/*
 * Determine whether or not the specified macro name exists for the
 * specified type of macro.
 */
BOOL
MacroExists(MACRO_TYPE type, const char * name)
{
	MACRO_HEADER *	head;

	head = FindMacroHeader(type);

	if (head == NULL)
		return FALSE;

	return (FindMacro(head, name) != NULL_MACRO);
}


/*
 * Expand a macro name of the specified type into its replacement word list.
 * The supplied ARGS structure is filled in with the count and table of words.
 * Returns TRUE if successful, or FALSE with an error message on error.
 */
BOOL
ExpandMacro(MACRO_TYPE type, const char * name, ARGS * retargs)
{
	MACRO_HEADER *	head;
	MACRO *		macro;

	retargs->table = NULL;
	retargs->count = 0;

	if (!isMacro(*name))
	{
		fprintf(stderr, "Macro name \"%s\" is not upper case\n", name);

		return FALSE;
	}

	head = FindMacroHeader(type);

	if (head == NULL)
		return FALSE;

	macro = FindMacro(head, name);

	if (macro == NULL_MACRO)
	{
		fprintf(stderr, "Macro name \"%s\" is undefined\n", name);

		return FALSE;
	}

	retargs->table = macro->values;
	retargs->count = macro->count;

	return TRUE;
}


/*
 * Define the specified macro name of the specified type to have the
 * following expansion.  The expansion is either in terms of space-separated
 * words, or just as a complete line.  Macro names must begin with an upper
 * case letter to distinguish them from non-macros.  Redefining of existing
 * macros is allowed; the new macros will simply be found first.
 * Returns TRUE if successful.
 */
BOOL
DefineMacro(MACRO_TYPE type, const char * name, const char * str)
{
	MACRO_HEADER *	head;		/* header of list */
	MACRO *		macro;		/* allocated macro */
	char *		bp;		/* pointer into buffer for alloc */
	const char *	word;		/* beginning of current word */
	int		wordCount;	/* number of words */
	int		size;		/* total size of allocated macro */
	int		i;
	int		wordLengths[MAX_WORDS];
	const char *	words[MAX_WORDS];

	if (*name == '\0')
	{
		fprintf(stderr, "Missing macro name for define\n");

		return FALSE;
	}

	if (!isMacro(*name))
	{
		fprintf(stderr, "Macro name \"%s\" is not upper case\n",
			name);

		return FALSE;
	}

	if (strlen(name) > MAX_MACRO_LEN)
	{
		fprintf(stderr, "Macro name \"%s\" is too long\n", name);

		return FALSE;
	}

	head = FindMacroHeader(type);

	if (head == NULL)
		return FALSE;

	size = sizeof(MACRO) + strlen(name) + 1;

	wordCount = 0;

	/*
	 * See if the input string is to be broken up into words.
	 * If so, then do that, otherwise leave the string as it is.
	 */
	if (head->words)
	{
		while (TRUE)
		{
			while (isBlank(*str))
				str++;

			if (*str == '\0')
				break;

			if (wordCount >= MAX_WORDS)
			{
				fprintf(stderr,
					"Too many words defined for macro \"%s\"\n",
					name);

				return FALSE;
			}

			word = str;

			while ((*str != '\0') && !isBlank(*str))
				str++;

			words[wordCount] = word;
			wordLengths[wordCount] = (str - word);

			size += (wordLengths[wordCount] + 1);

			wordCount++;
		}
	}
	else
	{
		if (wordCount >= MAX_WORDS)
		{
			fprintf(stderr,
				"Too many words defined for macro \"%s\"\n",
				name);

			return FALSE;
		}

		words[wordCount] = str;
		wordLengths[wordCount] = strlen(str);

		size += (wordLengths[wordCount] + 1);

		wordCount++;
	}

	size += (sizeof(char *) * (wordCount + 1));

	macro = (MACRO *) malloc(size);

	if (macro == NULL)
	{
		fprintf(stderr, "Cannot allocate macro structure\n");

		return FALSE;
	}

	bp = macro->buf;

	macro->values = (char **) bp;

	bp += (sizeof(char *) * (wordCount + 1));

	macro->name = bp;

	strcpy(bp, name);

	bp += (strlen(bp) + 1);

	for (i = 0; i < wordCount; i++)
	{
		macro->values[i] = bp;

		memcpy(bp, words[i], wordLengths[i]);

		bp += wordLengths[i];

		*bp++ = '\0';
	}

	macro->values[wordCount] = NULL;

	macro->count = wordCount;

	macro->next = head->list;
	head->list = macro;

	return TRUE;
}


static MACRO *
FindMacro(MACRO_HEADER * head, const char * name)
{
	MACRO *	macro;

	for (macro = head->list; macro; macro = macro->next)
	{
		if (strcmp(name, macro->name) == 0)
			return macro;
	}

	return NULL_MACRO;
}


/*
 * Find the header for the specified type of macro.
 * Returns NULL if the macro type is unknown.
 */
static MACRO_HEADER *
FindMacroHeader(MACRO_TYPE type)
{
	if ((type < 0) || (type >= MACRO_TYPE_COUNT))
	{
		fprintf(stderr, "Illegal macro type %d\n", type);

		return NULL;
	}

	return &macroLists[type];
}


/*
 * Display the definition of all the macros.
 * This description is suitable for reading back in as an initialization file.
 */
void
ListMacros(void)
{
	MACRO_TYPE		type;
	const MACRO_HEADER *	head;
	const MACRO *		macro;
	int			i;

	printf("%s\n", FIRST_LINE);
	printf("# System initialization file is \"%s\".\n", SYSTEM_INIT_FILE);

	for (type = 0; type < MACRO_TYPE_COUNT; type++)
	{
		head = &macroLists[type];

		for (macro = head->list; macro; macro = macro->next)
		{
			printf("%s %s", head->define, macro->name);

			for (i = 0; i < macro->count; i++)
				printf(" %s", macro->values[i]);

			printf("\n");
		}
	}
}

/* END CODE */
