/*  -*- Mode: C -*-  */

/* snprintfv.c --- printf clone for argv arrays */

/* Author:	       Gary V. Vaughan <gvv@techie.com>
 * Maintainer:	       Gary V. Vaughan <gvv@techie.com>
 * Created:	       Fri Nov 13 11:23:23 1998
 * Last Modified:      Thu Apr  6 02:20:12 2000
 *            by:      Gary V. Vaughan <gvv@techie.com>
 * ---------------------------------------------------------------------
 * @(#) $Id$
 * ---------------------------------------------------------------------
 */

/* Copyright (C) 1998, 1999, 2000 Gary V. Vaughan */

/* This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * As a special exception to the GNU General Public License, if you
 * distribute this file as part of a program that also links with and
 * uses the libopts library from AutoGen, you may include it under
 * the same distribution terms used by the libopts library.
 */

/* Code: */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <unistd.h>		/* for the write(2) call */

#define COMPILING_SNPRINTFV_C
#include "register.h"
#undef COMPILING_SNPRINTFV_C

#include "filament.h"
#include "stream.h"
#include "mem.h"

#define EOS			'\0'
#define SNV_CHAR_SPEC		'%'
#define SNV_ESC_SPEC		'\\'

/* We deliberately don't prototype the malloc functions;  they are cast
   to match the function pointers we expose to avoid compiler warnings
   from mismatched prototypes (if we find a host implementation.

   Not also that if this file is compiled -DWITH_DMALLOC, the inclusion
   in mem.h will cause the malloc references below to be redirected
   correctly. */
snv_pointer (*snv_malloc) PARAMS((size_t count)) 
		= (snv_pointer(*)PARAMS((size_t)))malloc;

snv_pointer (*snv_realloc) PARAMS((snv_pointer old, size_t count)) 
		= (snv_pointer(*)PARAMS((snv_pointer, size_t)))realloc;

void (*snv_free) PARAMS((snv_pointer old))
		= (void(*)PARAMS((snv_pointer old)))free;
char* (*snv_strdup) PARAMS((const char *str))
		= (char*(*)PARAMS((const char*)))strdup;


/* Functions to manage mapping of spec chars to handlers. */
static	unsigned spec_hash	PARAMS((unsigned spec));
static	void	spec_init	PARAMS((void));
static	spec_entry *spec_lookup	PARAMS((unsigned spec));
static	void	spec_insert 	PARAMS((spec_entry *pentry));

/* Convert a variadic va_list to an arg vector. */
static snv_constpointer *va_list_to_argv PARAMS((const char *format,
				   va_list ap));

/* FIXME:  We are assuming an ASCII character set where all the
           printable characters are between SPACE and DEL. */
#define ASCII_DEL	(int)''
#define ASCII_SPACE	(int)' '

/* TODO:  This is not thread-safe.  Change the API to pass the spec_table
          in as the first parameter to the functions which use it. */
static spec_entry *spec_table[ASCII_DEL - ASCII_SPACE];

static inline unsigned
spec_hash (spec)
    unsigned spec;
{
    return (spec & ASCII_DEL) - ASCII_SPACE;
}

/* Register all of the functions in INIT_SPEC_TABLE. */
static void
spec_init PARAMS((void))
{
    static boolean is_init = FALSE;
    
    if (!is_init)
    {
	extern spec_entry printfv_spec_table[];
	unsigned index = 0;

	memset(spec_table, 0, sizeof(spec_table));
	while (printfv_spec_table[index].spec != EOS)
	{
	    unsigned hash = spec_hash(printfv_spec_table[index].spec);
	    spec_table[hash] = printfv_spec_table + index;
	    index++;
	}

	is_init = TRUE;
    }
}

/* Insert PENTRY, a new handler, into SPEC_TABLE. */
static inline void
spec_insert (pentry)
    spec_entry *pentry;
{
    unsigned hash = spec_hash(pentry->spec);
    spec_init();
    spec_table[hash] = pentry;
}

/* Lookup and return the SPEC_TABLE entry for SPEC. */
static inline spec_entry*
spec_lookup (spec)
    unsigned spec;
{
    unsigned hash = spec_hash(spec);
    spec_init();
    return spec_table[hash];
}

/**
 * printfv_function_register: register.h
 * @spec: the character which will trigger @func, cast to an unsigned int.
 * @type: the type tag which will be returned by the format parser.
 * @func: the handler function.
 * 
 * Register @func to be called when @spec is encountered in a format
 * string.
 * 
 * Return value:
 * Returns SNV_OK if @func was successfuly registered, SNV_ERROR
 * otherwise.
 **/
int
printfv_function_register (spec, type, func)
    unsigned spec;
    int type;
    printfv_function *func;
{
    spec_entry *new =  snv_new(spec_entry, 1);
    
    new->spec = spec;
    new->type = type;
    new->func = func;

    spec_insert(new);

    return SNV_OK;
}


/**
 * printfv_argtype_renew: register.h
 * @type: a specifier type tag.
 * @pargtypes: the address of a vector of type tags.
 * @pargc: the number of elements allocated to @pargtypes.
 * @argindex: the zero based element number of @pargtypes to set to @type.
 * 
 * Helper function which will ensure that @pargtypes has been allocated
 * enough elements to accomodate @argindex by reallocating if necessary.
 * @pargtypes can be a pointer to a NULL pointer when calling this
 * function, and the @argindex numbered element will be set to @type,
 * and @pargc adjusted to reflect the new size of @pargtypes on return.
 **/
void
printfv_argtype_renew (type, pargtypes, pargc, argindex)
    int type;
    int **pargtypes;
    int *pargc;
    int argindex;
{
    /* FIXME: Don't blow your load so early!  systematically extend
              pargtypes and check for type mismatches on multiple
	      parameter uses. */
    
    *pargc = MAX(*pargc, 1+ argindex);
    
    if (*pargtypes == NULL)
    {
	/* No elements have been added to the type vector yet,
	   if the first element added is not element zero (i.e. from
	   a dollar specifier) we may need to add several elements. */
	*pargtypes = snv_new(int, *pargc);
    }
    else
    {
	*pargtypes = snv_renew(int, *pargtypes, *pargc);
    }

    (*pargtypes)[argindex] = type;
}


/* This structure is used to store the various elements of so-called
   "miscellaneous state information" in a parser. */
struct element {
    struct element *next;
    
    filament *key;		/* The element retrieval key. */
    snv_pointer data;		/* The associated data. */
    parser_delete_func *delete_func; /* Function to delete the data field. */
};

/* (re)initialise the memory used by PPARSER. */
static inline printfv_parser*
parser_init (pparser)
    printfv_parser *pparser;
{
    memset(pparser, 0, sizeof(printfv_parser));
    pparser->state = SNV_STATE_BEGIN;

    return pparser;
}

#if 0 /* Not actually used right now... */
/* Create, initialise and return a new printfv_parser structure. */
static inline printfv_parser*
parser_new PARAMS((void))
{
    printfv_parser *pparser;

    pparser = snv_new(printfv_parser, 1);
    pparser = parser_init(pparser);

    return pparser;
}
#endif

static void
parser_delete (parser)
    printfv_parser parser;
{
    /* Delete the error filament structure and its contents. */
    if (parser.error)
    {
	snv_delete(fildelete(parser.error));
    }
    
    while (parser.data)
    {
	struct element *stale = (struct element*)slist_head(parser.data);

	/* Advance the pointer to the next list element. */
	parser.data = slist_tail(parser.data);

	/* If the element has a delete_func, use it to delete the
	   data field. */
	if (stale != NULL && stale->delete_func != NULL)
	{
	    (*stale->delete_func)(stale->data);
	}

	/* Delete the key field. */
	snv_delete(fildelete(stale->key));
	
	/* Delete the element structure. */
	snv_delete(stale);
    }
}

static int
parser_data_cmp (try, refer)
    slist *try;
    slist *refer;
{
    return strcmp(filval(((struct element*)try)->key), (char*)refer);
}

/**
 * parser_data_get: register.h
 * @pparser: pointer to the current parser state.
 * @key: the storage key for the parser state data in question.
 * 
 * This function will attempt to retrieve data previously stored in
 * @pparser under @key.
 * 
 * Return value: 
 * If there is no data associated with @key, NULL is returned, otherwise
 * the address of the stored data is returned (so that 0 valued data can
 * be distinguished from a NULL return).
 **/ 
snv_pointer*
parser_data_get (pparser, key)
    printfv_parser *pparser;
    const char *key;
{
    struct element *elt;

    elt = (struct element*)slist_get(pparser->data, (slist*)key,
				     parser_data_cmp);
    return elt ? &(elt->data) : NULL;
}

/**
 * parser_data_set: register.h
 * @pparser: pointer to the current parser state.
 * @key: the storage key for the parser state data in question.
 * @data: the data to be associated with @key in @pparser.
 * @delete_func: a function which will correctly free @data in due course.
 * 
 * This function will store the arbitrary stae information, @data, in
 * @pparser so that it can be shared with the other handler functions
 * which implement the format string parsing fsm.  @data is stored with
 * an associated @key for later retrieval.  @key is copied into @pparser.
 * so it is safe to pass static strings or recycle the original.  Any
 * stale @data already stored against @key in @pparser is passed to the
 * stale @delete_func for recycling, before the new @data and @delete_func
 * are used to overwrite the stale values.
 **/
void
parser_data_set (pparser, key, data, delete_func)
    printfv_parser *pparser;
    const char *key;
    snv_pointer data;
    parser_delete_func *delete_func;
{
    struct element *new;

    new = (struct element*)slist_get(pparser->data, (snv_pointer)key,
				     parser_data_cmp);
    if (new == NULL)
    {
	new		= snv_new(struct element, 1);
	new->key	= filnew(key, strlen(key));
	new->data	= data;
	new->delete_func= delete_func;

	pparser->data	= slist_cons((slist*)new, pparser->data);
    }
    else
    {
	/* If the element has a delete_func, use it to delete the
	   data field. */
	if (new->delete_func != NULL)
	{
	    (*new->delete_func)(new->data);
	}
	new->data	= data;
	new->delete_func= delete_func;
    }
}

/**
 * parser_data_delete: destructor register.h
 * @address: the address of the data to be recycled.
 * 
 * The memory associated with @address is released.
 *
 * This is a sample delete function which can be used with
 * parser_data_set() to delete data which was created with a single
 * memory allocation;  that is, the memory at @address contains no
 * pointers to other allocated memory which should be recycled and
 * is not itself statically allocated.
 **/
void
parser_data_delete (address)
    snv_pointer address;
{
    snv_delete(address);
}

/**
 * parser_error: register.h
 * @pparser: pointer to the current parser state.
 * @error_message: new error message to append to @pparser.
 * 
 * The contents of @error_message are appended to the @pparser internal
 * error string, so it is safe to pass static strings or recycle the
 * original when this function returns.
 * 
 * Return value:
 * The address of the full accumulated error message in @pparser is
 * returned.
 **/
char*
parser_error (pparser, error_message)
    printfv_parser *pparser;
    const char *error_message;
{
    if (pparser->error == NULL)
    {
	pparser->error = filnew(NULL, 0);
    }

    return filcat(pparser->error, error_message);
}

static snv_constpointer*
va_list_to_argv (format, ap)
    const char *format;
    va_list ap;
{
    int count_or_errorcode = 0;
    snv_constpointer *args = NULL;
    int argcount = 0, argc = 0;
    int *argtypes = NULL;
    const char *pformat = format;

    return_val_if_fail (format != NULL, NULL);

    while (!(*pformat == EOS || count_or_errorcode < 0))
    {
	int ch = (int)*pformat++;

	switch (ch)
	{
	case SNV_CHAR_SPEC:
	    if (*pformat != SNV_CHAR_SPEC)
	    {
		/* We found the start of a format specifier! */
		printfv_parser parser;

		parser_init(&parser);
		while (count_or_errorcode >= 0
		       && parser.state != SNV_STATE_END)
		{
		    /* Until we fill the stream (or ger some other
		       exception) or one of the handlers tells us
		       we have reached the end of the specifier... */
		    spec_entry *data;
		    int status;

		    /* ...lookup the handler associated with the char
		       we are looking at in the format string... */
		    data = spec_lookup(*pformat);
		    if (data == NULL)
		    {
			PARSER_ERROR(&parser, "unregistered specifier");
			return NULL;
		    }

		    /* ...and call the relevant handler. Notice that there
		       is no stream specified (i.e. we don't want output)
		       and that we have specified somewere to store the
		       argtypes. */
		    status = (*data->func)(&parser, NULL, &pformat, NULL,
					   &argc, &argtypes, &argcount);
		    
		    count_or_errorcode = (status < 0) ? status
			: count_or_errorcode + status;

		}
		parser_delete(parser);
		break;
	    }
	    /* An escaped CHAR_SPEC: ignore it (by falling through). */
	    ++pformat;
	    /*NOBREAK*/

	default: /* Just a character: ignore it. */
	    break;
	}
    }

    if (argc > 0)
    {
	int index = 0;

	args = snv_new(snv_constpointer, argc);
	while (index < argc
	       && count_or_errorcode >= 0)
	{
	    /* Scan the format string to find the type of the argument
	       so we can cast it and store it correctly.

	       Note that some of these look wierd, but according to the
	       ISO C standards, standard type promotion takes place
	       on any variadic arguments as they are aligned on the call
	       stack, and so it is these promoted types that we must
	       extract with the va_arg() macro, or the alignment gets
	       all messed up.

	       Thanks to Robert Lipe <robertlipe@usa.net> for explaining all
	       this to me. */
	    switch(argtypes[index] & SNV_TYPE_MASK)
	    {
	    case SNV_INT:
		if (argtypes[index] & (SNV_FLAG_LONG|SNV_FLAG_UNSIGNED))
		{
		    args[index] = (snv_constpointer)snv_new(unsigned long, 1);
		    *(unsigned long*)args[index] = va_arg(ap, unsigned long);
		}
		else if (argtypes[index] & SNV_FLAG_LONG)
		{
		    args[index] = (snv_constpointer)snv_new(long, 1);
		    *(long*)args[index] = va_arg(ap, long);
		}
		/* The ISO C variadic argument promotion rules obsolete
		   this section:
		else if (argtypes[index] & (SNV_FLAG_SHORT|SNV_FLAG_UNSIGNED))
		{
		    args[index] = SNV_UINT_TO_POINTER(va_arg(ap, unsigned));
		}
		else if (argtypes[index] & SNV_FLAG_SHORT)
		{
		    args[index] = SNV_INT_TO_POINTER(va_arg(ap, int));
		}
		*/
		else if (argtypes[index] & SNV_FLAG_UNSIGNED)
		{
		    args[index] = SNV_UINT_TO_POINTER(va_arg(ap, unsigned));
		}
		else
		{
		    args[index] = SNV_INT_TO_POINTER(va_arg(ap, int));
		}
		break;
	    case SNV_FLOAT:
		/* The ISO C variadic argument promotion rules obsolete
		   this section:
		args[index] = (snv_constpointer)snv_new(float, 1);
		*(float*)args[index] = va_arg(ap, float);
		break;
		*/
	    case SNV_DOUBLE:
		args[index] = (snv_constpointer)snv_new(double, 1);
		*(double*)args[index] = va_arg(ap, double);
		break;
	    case SNV_WCHAR:
		/* The ISO C variadic argument promotion rules obsolete
		   this section:
		args[index] =
		    (snv_constpointer)SNV_INT_TO_POINTER(va_arg(ap, int));
		break;
		*/
	    case SNV_CHAR:
		args[index] =
		    (snv_constpointer)SNV_INT_TO_POINTER(va_arg(ap, int));
		break;
	    default:
		args[index] = va_arg(ap, snv_constpointer);
		break;
	    }
	    index++;
	}
    }
    
    /* We don't save this type information, because it is already encoded
       in the format string, and when the handlers use the arg vector they
       will use the format string to work out type information (as they did
       when called in the first pass to give us this information). */
    if (argtypes != NULL)
    {
	snv_delete(argtypes);
    }
    
    return args;
}


/* This is the parser driver.

   Here we scan through the format string and move bytes into the stream
   and call handlers based on the parser state. */

/**
 * stream_printfv: snprintfv.h
 * @stream: an initialised stream structure.
 * @format: a % delimited format string.
 * @args: a vector of argument addresses to match @format.
 * 
 * Format the elements of @args according to @format, and write
 * the results to @stream.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
stream_printfv (stream, format, args)
    STREAM *stream;
    const char *format;
    snv_constpointer const args[];
{
    const char *pformat = format;
    int argindex = 0, argc = 0;
    int count_or_errorcode = SNV_OK;

    /* Keep going until the format string runs out! */
    while (!(*pformat == EOS || count_or_errorcode < 0))
    {
	int ch = (int)*pformat++;

	switch (ch)
	{
	case SNV_CHAR_SPEC:
	    if (*pformat != SNV_CHAR_SPEC)
	    {
		/* We found the start of a format specifier! */
		printfv_parser parser;

		parser_init(&parser);
		while (count_or_errorcode >= 0
		       && parser.state != SNV_STATE_END)
		{
		    /* Until we fill the stream (or get some other
		       exception) or one of the handlers tells us
		       we have reached the end of the specifier... */
		    spec_entry *data;
		    int status;

		    /* ...lookup the handler associated with the char
		       we are looking at in the format string... */
		    data = spec_lookup(*pformat);
		    if (data == NULL)
		    {
			PARSER_ERROR(&parser, "unregistered specifier");
			return -1;
		    }

		    /* ...and call the relevant handler. */
		    status = (*data->func)(&parser, stream, &pformat,
					   args, &argc, NULL, &argindex);
		
		    count_or_errorcode = (status < 0) ? status
			: count_or_errorcode + status;

		}
		parser_delete(parser);
		break;
	    }
	    /* An escaped CHAR_SPEC: copy it (by falling through). */
	    ++pformat;
	    /*NOBREAK*/

	default: /* Just a character: copy it. */
	{
	    int status = stream_put(ch, stream);
	    count_or_errorcode = (status < 0)
				? status
				: count_or_errorcode + status;
	    break;
	}
	}
    }

    return count_or_errorcode;
}

/**
 * stream_vprintf: snprintfv.h
 * @stream: an initialised stream structure.
 * @format: a % delimited format string.
 * @ap: a varargs/stdargs va_list.
 * 
 * Format the elements of @ap according to @format, and write
 * the results to @stream.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
stream_vprintf (stream, format, ap)
    STREAM *stream;
    const char *format;
    va_list ap;
{
    snv_constpointer *args = va_list_to_argv(format, ap);
    int count_or_errorcode = stream_printfv(stream, format, args);

    /* FIXME: leaks memory for pointers to malloced mem. */
    if (args) snv_delete(args);
    return count_or_errorcode;
}

/**
 * stream_printf: snprintfv.h
 * @stream: an initialised stream structure.
 * @format: a % delimited format string.
 * @va_alist: a varargs/stdargs va_list.
 * 
 * Format the elements of @va_alist according to @format, and write
 * the results to the standard output stream.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
#ifdef SNV_USING_STDARG_H
stream_printf (STREAM *stream, const char *format, ...)
#else    
stream_printf (stream, format, va_alist)
    STREAM *stream;
    const char *format;
    va_dcl
#endif
{
    int count_or_errorcode;
    va_list ap;

    VA_START(ap, format);
    count_or_errorcode = stream_vprintf(stream, format, ap);
    VA_END(ap);
    
    return count_or_errorcode;
}


/*  Finally... the main API implementation: */

/**
 * fdputc: snprintfv.h
 * @ch: A single character to be added to @stream.
 * @stream: The stream in which to write @ch.
 * 
 * A stream_put_function function for use in putting characters
 * into STREAMs holding a file descriptor.
 * 
 * Return value: 
 * The value of @ch that has been put in @stream, or -1 in case of
 * an error (errno will be set to indicate the type of error).
 **/
int
fdputc (ch, stream)
    int ch;
    STREAM *stream;
{
    static char buf[2] = "c";
    *buf = (char)ch;
    return write(SNV_POINTER_TO_INT(stream->stream), buf, 1) ? ch : -1;
}

/**
 * snv_dprintf: snprintfv.h
 * @fd: an open file descriptor.
 * @format: a % delimited format string.
 * @va_alist: a varargs/stdargs va_list.
 * 
 * Format the elements of @va_alist according to @format, and write
 * the results to the file descriptor @fd.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
#ifdef SNV_USING_STDARG_H
snv_dprintf (int fd, const char *format, ...)
#else
snv_dprintf (fd, format, va_alist)
    int fd;
    const char *format;
    va_dcl
#endif
{
    int count_or_errorcode;
    va_list ap;

    VA_START(ap, format);
    count_or_errorcode = snv_vdprintf(fd, format, ap);
    VA_END(ap);
    
    return count_or_errorcode;
}

/**
 * snv_vdprintf: snprintfv.h
 * @fd: an open file descriptor.
 * @format: a % delimited format string.
 * @ap: a varargs/stdargs va_list.
 * 
 * Format the elements of @ap according to @format, and write
 * the results to the file descriptor @fd.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
snv_vdprintf (fd, format, ap)
    int fd;
    const char *format;
    va_list ap;
{
    STREAM out;
    stream_init(&out, (stream_gpointer)SNV_INT_TO_POINTER(fd),
		SNV_UNLIMITED, NULL, fdputc);

    return stream_vprintf(&out, format, ap);
}    

/**
 * dprintfv: snprintfv.h
 * @fd: an open file descriptor.
 * @format: a % delimited format string.
 * @args: a vector of argument addresses to match @format.
 * 
 * Format the elements of @args according to @format, and write
 * the results to file descriptor @fd.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
dprintfv (fd, format, args)
    int fd;
    const char *format;
    snv_constpointer const args[];
{
    STREAM out;			/* allocated in this stack frame */
    stream_init(&out, (stream_gpointer)SNV_INT_TO_POINTER(fd),
		SNV_UNLIMITED, NULL, fdputc);
    return stream_printfv(&out, format, args);
}
    

/**
 * fileputc: snprintfv.h
 * @ch: A single character to be added to @stream.
 * @stream: The stream in which to write @ch.
 * 
 * A stream_put_function function for use in putting characters
 * into STREAMs holding a FILE*.
 * 
 * Return value: 
 * The value of @ch that has been put in @stream.
 **/
int
fileputc (ch, stream)
    int ch;
    STREAM *stream;
{
    return fputc(ch, (FILE*)stream->stream);
}

/**
 * snv_printf: snprintfv.h
 * @format: a % delimited format string.
 * @va_alist: a varargs/stdargs va_list.
 * 
 * Format the elements of @va_alist according to @format, and write
 * the results to the standard output stream.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
#ifdef SNV_USING_STDARG_H
snv_printf (const char *format, ...)
#else
snv_printf (format, va_alist)
    const char *format;
    va_dcl
#endif
{
    int count_or_errorcode;
    va_list ap;

    VA_START(ap, format);
    count_or_errorcode = snv_vprintf(format, ap);
    VA_END(ap);
    
    return count_or_errorcode;
}

/**
 * snv_vprintf: snprintfv.h
 * @format: a % delimited format string.
 * @ap: a varargs/stdargs va_list.
 * 
 * Format the elements of @ap according to @format, and write
 * the results to the standard output stream.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
snv_vprintf (format, ap)
    const char *format;
    va_list ap;
{
    STREAM out;
    stream_init(&out, (stream_gpointer)stdout, SNV_UNLIMITED, NULL,
		fileputc);

    return stream_vprintf(&out, format, ap);
}

/**
 * printfv: snprintfv.h
 * @format: a % delimited format string.
 * @args: a vector of argument addresses to match @format.
 * 
 * Format the elements of @args according to the string @format,
 * and write the result to the standard output stream.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
printfv (format, args)
    const char *format;
    snv_constpointer const args[];
{
    STREAM out;			/* allocated in this stack frame */
    stream_init(&out, (stream_gpointer)stdout, SNV_UNLIMITED, NULL,
		fileputc);
    return stream_printfv(&out, format, args);
}

/**
 * snv_fprintf: snprintfv.h
 * @file: a stdio.h FILE* stream.
 * @format: a % delimited format string.
 * @va_alist: a varargs/stdargs va_list.
 * 
 * Format the elements of @va_alist according to @format, and write
 * the results to the @file stream.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
#ifdef SNV_USING_STDARG_H
snv_fprintf (FILE *file, const char *format, ...)
#else
snv_fprintf (file, format, va_alist)
    FILE *file;
    const char *format;
    va_dcl
#endif
{
    int count_or_errorcode;
    va_list ap;

    VA_START(ap, format);
    count_or_errorcode = snv_vfprintf(file, format, ap);
    VA_END(ap);
    
    return count_or_errorcode;
}

/**
 * snv_vfprintf: snprintfv.h
 * @file: a stdio.h FILE* stream.
 * @format: a % delimited format string.
 * @ap: a varargs/stdargs va_list.
 * 
 * Format the elements of @ap according to @format, and write
 * the results to the @file stream.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
snv_vfprintf (file, format, ap)
    FILE *file;
    const char *format;
    va_list ap;
{
    STREAM out;
    stream_init(&out, (stream_gpointer)file, SNV_UNLIMITED, NULL, fileputc);

    return stream_vprintf(&out, format, ap);
}    

/**
 * fprintfv: snprintfv.h
 * @file: a stdio.h FILE* stream.
 * @format: a % delimited format string.
 * @args: a vector of argument addresses to match @format.
 * 
 * Format the elements of @args according to @format, and write
 * the results to @file.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
fprintfv (file, format, args)
    FILE *file;
    const char *format;
    snv_constpointer const args[];
{
    STREAM out;			/* allocated in this stack frame */
    stream_init(&out, (stream_gpointer)file, SNV_UNLIMITED, NULL,
		fileputc);
    return stream_printfv(&out, format, args);
}


/**
 * bufputc: snprintfv.h
 * @ch: A single character to be added to @stream.
 * @stream: The stream in which to write @ch.
 * 
 * A stream_put_function function for use in putting characters
 * into STREAMs holding a char buffer.
 * 
 * Return value:
 * The value of @ch that has been put in @stream.
 **/
int
bufputc (ch, stream)
    int ch;
    STREAM *stream;
{
    char **ppbuffer = (char**)stream->stream;
    **ppbuffer = (char)ch;
    (*ppbuffer)++;
    return ch;
}

/**
 * snv_sprintf: snprintfv.h
 * @buffer: a preallocated char* buffer.
 * @format: a % delimited format string.
 * @va_alist: a varargs/stdargs va_list.
 * 
 * Format the elements of @va_alist according to @format, and write
 * the results to the string @buffer.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
#ifdef SNV_USING_STDARG_H
snv_sprintf (char buffer[], const char *format, ...)
#else
snv_sprintf (buffer, format, va_alist)
    char buffer[];
    const char *format;
    va_dcl
#endif
{
    int count_or_errorcode;
    va_list ap;

    VA_START(ap, format);
    count_or_errorcode = snv_vsprintf(buffer, format, ap);
    VA_END(ap);

    return count_or_errorcode;
}

/**
 * snv_vsprintf: snprintfv.h
 * @buffer: a preallocated char* buffer.
 * @format: a % delimited format string.
 * @ap: a varargs/stdargs va_list.
 * 
 * Format the elements of @ap according to @format, and write
 * the results to the string @buffer.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
snv_vsprintf (buffer, format, ap)
    char buffer[];
    const char *format;
    va_list ap;
{
    int count_or_errorcode;
    STREAM out;
    stream_init(&out, (stream_gpointer)&buffer, SNV_UNLIMITED, NULL, bufputc);
    count_or_errorcode = stream_vprintf(&out, format, ap);

    /* Bah.  This is messy because we need to explicity put an end-of-string
       marker at the end of the real output. */
    if (count_or_errorcode >= 0)
    {
        /* Terminate with an EOS without incrementing the counter. */
        count_or_errorcode = (stream_put(EOS, &out) != EOF)
	                   ? count_or_errorcode : EOF;
    }

    return count_or_errorcode;
}    

/**
 * sprintfv: snprintfv.h
 * @buffer: a preallocated char* buffer.
 * @format: a % delimited format string.
 * @args: a vector of argument addresses to match @format.
 * 
 * Format the elements of @args according to @format, and write
 * the results to the string @buffer.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
sprintfv (buffer, format, args)
    char buffer[];
    const char *format;
    snv_constpointer const args[];
{
    int count_or_errorcode;
    STREAM out;			/* allocated in this stack frame */
    stream_init(&out, (stream_gpointer)&buffer, SNV_UNLIMITED, NULL, bufputc);
    count_or_errorcode = stream_printfv(&out, format, args);

    /* Bah.  This is messy because we need to explicity put an end-of-string
       marker at the end of the real output. */
    if (count_or_errorcode >= 0)
    {
        /* Terminate with an EOS without incrementing the counter. */
        count_or_errorcode = (stream_put(EOS, &out) != EOF)
	                   ? count_or_errorcode : EOF;
    }

    return count_or_errorcode;
}

/**
 * snv_snprintf: snprintfv.h
 * @buffer: a preallocated char* buffer.
 * @limit: the maximum number of characters to write into @buffer.
 * @format: a % delimited format string.
 * @va_alist: a varargs/stdargs va_list.
 * 
 * Format the elements of @va_alist according to @format, and write
 * the results to the string @buffer, truncating the formatted string
 * if it reaches @limit characters in length.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
#ifdef SNV_USING_STDARG_H
snv_snprintf (char buffer[], unsigned long limit, const char *format, ...)
#else
snv_snprintf (buffer, limit, format, va_alist)
    char buffer[];
    unsigned long limit;
    const char *format;
    va_dcl
#endif
{
    int count_or_errorcode;
    va_list ap;

    VA_START(ap, format);
    count_or_errorcode = snv_vsnprintf(buffer, limit, format, ap);
    VA_END(ap);

    return count_or_errorcode;
}

/**
 * snv_vsnprintf: snprintfv.h
 * @buffer: a preallocated char* buffer.
 * @limit: the maximum number of characters to write into @buffer.
 * @format: a % delimited format string.
 * @ap: a varargs/stdargs va_list.
 * 
 * Format the elements of @ap according to @format, and write
 * the results to the string @buffer, truncating the formatted string
 * if it reaches @limit characters in length.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
snv_vsnprintf (buffer, limit, format, ap)
    char buffer[];
    unsigned long limit;
    const char *format;
    va_list ap;
{
    int count_or_errorcode;
    STREAM out;
    stream_init(&out, (stream_gpointer)&buffer, limit, NULL, bufputc);
    count_or_errorcode = stream_vprintf(&out, format, ap);

    /* Bah.  This is messy because we need to explicity put an end-of-string
       marker at the end of the real output. */
    if (count_or_errorcode >= 0)
    {
        /* Terminate with an EOS without incrementing the counter. */
        count_or_errorcode = (stream_put(EOS, &out) != EOF)
	                   ? count_or_errorcode : EOF;
    }

    return count_or_errorcode;
}    

/**
 * snprintfv: snprintfv.h
 * @buffer: a preallocated char* buffer.
 * @limit: the maximum number of characters to write into @buffer.
 * @format: a % delimited format string.
 * @args: a vector of argument addresses to match @format.
 * 
 * Format the elements of @args according to @format, and write
 * the results to the string @buffer, truncating the formatted string
 * if it reaches @limit characters in length.
 * 
 * Return value:
 * The number of characters written is returned, unless there is
 * an error, when %SNV_ERROR is returned.
 **/
int
snprintfv (buffer, limit, format, args)
    char buffer[];
    unsigned long limit;
    const char *format;
    snv_constpointer const args[];
{
    int count_or_errorcode;
    STREAM out;
    stream_init(&out, (stream_gpointer)&buffer, limit, NULL, bufputc);
    count_or_errorcode = stream_printfv(&out, format, args);

    /* Bah.  This is messy because we need to explicity put an end-of-string
       marker at the end of the real output. */
    if (count_or_errorcode >= 0)
    {
        /* Terminate with an EOS without incrementing the counter. */
        count_or_errorcode = (stream_put(EOS, &out) != EOF)
	                   ? count_or_errorcode : EOF;
    }

    return count_or_errorcode;
}


/**
 * filputc: snprintfv.h
 * @ch: A single character to be added to @stream.
 * @stream: The stream in which to write @ch.
 * 
 * A stream_put_function function for use in putting characters
 * into STREAMs holding a filament*.
 * 
 * Return value: 
 * The value of @ch that has been put in @stream.
 **/
int
filputc (ch, stream)
    int ch;
    STREAM *stream;
{
    return filccat((filament*)stream->stream, ch), ch;
}

/**
 * snv_asprintf: snprintfv.h
 * @format: a % delimited format string.
 * @va_alist: a varargs/stdargs va_list.
 * 
 * Format the elements of @va_alist according to @format, and write
 * the results to an internally allocated buffer.
 * 
 * Return value:
 * The address of the memory which holds the formatted string is
 * returned (and should be freed by the caller), unless there is
 * an error, when NULL is returned.
 **/
char*
#ifdef SNV_USING_STDARG_H
snv_asprintf (const char *format, ...)
#else
snv_asprintf (format, va_alist)    
    const char *format;
    va_dcl
#endif
{
    char *result;
    va_list ap;

    VA_START(ap, format);
    result = snv_vasprintf(format, ap);
    VA_END(ap);

    return result;
}

/**
 * snv_vasprintf: snprintfv.h
 * @format: a % delimited format string.
 * @ap: a varargs/stdargs va_list.
 * 
 * Format the elements of @ap according to @format, and write
 * the results to an internally allocated buffer.
 * 
 * Return value:
 * The address of the memory which holds the formatted string is
 * returned (and should be freed by the caller), unless there is
 * an error, when NULL is returned.
 **/
char*
snv_vasprintf (format, ap)
    const char *format;
    va_list ap;
{
    int count_or_errorcode;
    char *result;
    STREAM out;
    filament *fil = filnew(NULL, 0);
    stream_init(&out, (stream_gpointer)fil, SNV_UNLIMITED, NULL, filputc);
    count_or_errorcode = stream_vprintf(&out, format, ap);

    /* Be careful to recycle this -- it is not allocated on the stack
       like the stream which holds its address */
    result = fildelete(fil);

    return (count_or_errorcode < 0) ? NULL : result;
}

/**
 * asprintfv: snprintfv.h
 * @format: a % delimited format string.
 * @args: a vector of argument addresses to match @format.
 * 
 * Format the elements of @args according to @format, and write
 * the results to an internally allocated buffer.
 * 
 * Return value:
 * The address of the memory which holds the formatted string is
 * returned (and should be freed by the caller), unless there is
 * an error, when NULL is returned.
 **/
char*
asprintfv (format, args)
    const char *format;
    snv_constpointer const args[];
{
    int count_or_errorcode;
    char *result;
    STREAM out;
    filament *fil = filnew(NULL, 0);
    stream_init(&out, (stream_gpointer)fil, SNV_UNLIMITED, NULL, filputc);
    count_or_errorcode = stream_printfv(&out, format, args);

    /* Be careful to recycle this -- it is not allocated on the stack
       like the stream which holds its address */
    result = fildelete(fil);

    return (count_or_errorcode < 0) ? NULL : result;
}

/* snprintfv.c ends here */
