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

/* format.c ---  */

/* Author:	       Gary V. Vaughan <gvv@techie.com>
 * Maintainer:	       Gary V. Vaughan <gvv@techie.com>
 * Created:	       Thu Apr 22 23:13:34 1999
 * Last Modified:      Thu Sep 23 23:03:15 1999
 *            by:      Gary V. Vaughan <gvv@techie.com>
 * ---------------------------------------------------------------------
 * @(#) $Id$
 * ---------------------------------------------------------------------
 */

/* Copyright (C) 1999 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 "snprintfv/register.h"
#include "snprintfv/mem.h"

#if 0
/* libltdl requires this */
#define printfv_spec_table	format_LTX_printfv_spec_table
#endif

/* States needed to support:
   %[<number>$][<flag>*][<width>|\*][.<precision>|\*][<modifier>*]<specifier>
*/
#define SNV_STATE_FLAG		(1 << 1)
#define SNV_STATE_WIDTH		(1 << 2)
#define SNV_STATE_PRECISION	(1 << 3)
#define SNV_STATE_MODIFIER	(1 << 4)
#define SNV_STATE_SPECIFIER	(1 << 5)

/* This is where the parsing of FORMAT strings is handled:

   Each of these functions should inspect PPFORMAT and PPARSER for parser
   state information;  update PPFORMAT and PPARSER as necessary based on
   the state discovered;  possibly put some characters in STREAM, in
   which case that number of characters must be returned.  If the
   handler detects that parsing (of the current specifier) is complete,
   then it must set info->state to SNV_STATE_END.  The library will then
   copy characters from PPFORMAT to STREAM until another unescaped
   SNV_CHAR_SPEC is detected when the handlers will be called again. */
   
static DECLARE_PRINTFV_HANDLER(number_handler);
static DECLARE_PRINTFV_HANDLER(flag_handler);
static DECLARE_PRINTFV_HANDLER(modifier_handler);

static DECLARE_PRINTFV_HANDLER(char_specifier_handler);
static DECLARE_PRINTFV_HANDLER(decimal_specifier_handler);
static DECLARE_PRINTFV_HANDLER(hex_specifier_handler);
static DECLARE_PRINTFV_HANDLER(octal_specifier_handler);
static DECLARE_PRINTFV_HANDLER(string_specifier_handler);
static DECLARE_PRINTFV_HANDLER(unsigned_specifier_handler);

spec_entry printfv_spec_table[] =
{
    /* ch  type		function */
    { ' ', 0,		flag_handler },
    { '#', 0,		flag_handler },
    { '+', 0,		flag_handler },
    { '-', 0,		flag_handler },
    { '\'',0,		flag_handler },
    { '*', SNV_INT,	number_handler },
    { '.', 0,		number_handler },
    { '0', 0,		flag_handler },
    { '1', 0,		number_handler },
    { '2', 0,		number_handler },
    { '3', 0,		number_handler },
    { '4', 0,		number_handler },
    { '5', 0,		number_handler },
    { '6', 0,		number_handler },
    { '7', 0,		number_handler },
    { '8', 0,		number_handler },
    { '9', 0,		number_handler },
    { 'c', SNV_CHAR,	char_specifier_handler },
    { 'd', SNV_INT,	decimal_specifier_handler },
    { 'h', 0,		modifier_handler },
    { 'i', SNV_INT,	decimal_specifier_handler },
    { 'l', 0,		modifier_handler },
    { 'L', 0,		modifier_handler },
    { 'o', SNV_INT,	octal_specifier_handler },
    { 'q', 0,		modifier_handler },
    { 's', SNV_STRING,	string_specifier_handler },
    { 'u', SNV_INT,	unsigned_specifier_handler },
    { 'x', SNV_INT,	hex_specifier_handler },
    { 'X', SNV_INT,	hex_specifier_handler },
    { '\0',SNV_LAST,	NULL }
};

static inline char fetch_space_defaulted_char PARAMS((printfv_parser *pparser,
				    const char * const key));
static inline unsigned fetch_zero_defaulted_uint PARAMS((printfv_parser *pparser,
				    const char * const key));
static int	fetch_argv_defaulted_int PARAMS((printfv_parser *pparser,
				    const char * const key,
				    snv_constpointer const *argv,
				    int *pargindex));
static long	fetch_argv_long PARAMS((printfv_parser *pparser,
				    snv_constpointer const *argv,
				    int index));
static unsigned long fetch_argv_ulong PARAMS((printfv_parser *pparser,
				    snv_constpointer const *argv,
				    int index));
static int	format_number	PARAMS((printfv_parser * const pparser,
				    STREAM *stream,
				    const char **ppformat,
				    snv_constpointer const *argv,
				    int *pargc,
				    int **pargtypes,
				    int *pargindex,
				    unsigned base));


/* Maintain the count while putting ch in stream, also be careful about
   handling NULL stream if the handler is being called purely to count
   arguments or output size. */
#define EMIT(ch, stream, count)	SNV_STMT_START {		\
    int m_status = stream ? stream_put(ch, stream) : 1;	\
    count = (m_status < 0) ? m_status : count + m_status;	\
} SNV_STMT_END

/* These are the flag bits used with the "flags" key */
#define FORMAT_TYPE_MASK	0x0
#define FORMAT_FLAG_MASK	~FORMAT_TYPE_MASK
#define FORMAT_FLAG_ALT		(1 << 1)
#define FORMAT_FLAG_LEFT	(1 << 2)
#define FORMAT_FLAG_SPACE	(1 << 3)
#define FORMAT_FLAG_SHOWSIGN	(1 << 4)
#define FORMAT_FLAG_GROUP	(1 << 5)
/* We reuse the SNV_FLAG_foo macros for the type flags. */

#undef  ISSET
#define ISSET(field, flag)	((field)&(flag))


static inline char
fetch_space_defaulted_char (pparser, key)
    printfv_parser *pparser;
    const char * const key;
{
    snv_pointer *pvalue;
    pvalue = parser_data_get(pparser, key);
    return (pvalue == NULL) ? ' ' : (char)SNV_POINTER_TO_INT(*pvalue);
}

static inline unsigned
fetch_zero_defaulted_uint (pparser, key)
    printfv_parser *pparser;
    const char * const key;
{
    snv_pointer *pvalue;
    pvalue = parser_data_get(pparser, key);
    return (pvalue == NULL) ? 0 : SNV_POINTER_TO_UINT(*pvalue);
}


static long
fetch_argv_long (pparser, argv, index)
    printfv_parser *pparser;
    snv_constpointer const *argv;
    int index;
{
    long result		= 0L;
    unsigned flags  	= fetch_zero_defaulted_uint(pparser, "flags");

    /* parser read '*', and should get value from argument vector */
    if (ISSET(flags, SNV_FLAG_LONG))
    {
	/* A long may be larger than a char*, so we stored the address
	   of the data to avoid losing information in the cast. */
	result = *(long*)argv[index];
    }
    else
    {
	/* Any other integral type can fit in a pointer, so we stored
	   the data directly in the word. (we don't support long long
	   for the time being). */
	result = (long)SNV_POINTER_TO_INT(argv[index]);
    }

    return result;
}

static unsigned long
fetch_argv_ulong (pparser, argv, index)
    printfv_parser *pparser;
    snv_constpointer const *argv;
    int index;
{
    unsigned long result = 0L;
    unsigned flags  	 = fetch_zero_defaulted_uint(pparser, "flags");

    /* parser read '*', and should get value from argument vector */
    if (ISSET(flags, SNV_FLAG_LONG))
    {
	/* A long may be larger than a char*, so we stored the address
	   of the data to avoid losing information in the cast. */
	result = *(unsigned long*)argv[index];
    }
    else
    {
	/* Any other integral type can fit in a pointer, so we stored
	   the data directly in the word. (we don't support long long
	   for the time being). */
	result = (unsigned long)SNV_POINTER_TO_UINT(argv[index]);
    }

    return result;
}


static int
fetch_argv_defaulted_int (pparser, key, argv, pargindex)
    printfv_parser *pparser;
    const char * const key;
    snv_constpointer const *argv;
    int *pargindex;
{
    int result, *pvalue;
    
    pvalue = (int*)parser_data_get(pparser, key);

    if (pvalue == NULL)
    {
	/* parser didn't read an explicit value */
	result = 0;
    }

    /* Be careful about being called without an arg array and/or index
       address, when parsing a format to extract argument types for
       example. */
    else if ((*pvalue == INT_MIN) && (pargindex != NULL))
    {
	if (argv != NULL)
	{
	    /* FIXME: need to check for integer overflow... */
	    result = (int)fetch_argv_ulong(pparser, argv, *pargindex);
	}
	
	/* increment the argument index */
	(*pargindex)++;
    }
    else
    {
	/* Parser read an explicit value. */
	result =  *pvalue;
    }
    
    return result;
}


static
DEFINE_PRINTFV_HANDLER(flag_handler)
{
    unsigned flags;

    return_val_if_fail(pparser != NULL, SNV_ERROR);

    if (!(pparser->state & (SNV_STATE_BEGIN|SNV_STATE_FLAG)))
    {
	PARSER_ERROR(pparser, "invalid specifier");
	return -1;
    }
    
    flags  = fetch_zero_defaulted_uint(pparser, "flags");
    pparser->state = SNV_STATE_FLAG;
    
    while (pparser->state & SNV_STATE_FLAG)
    {
	switch (**ppformat)
	{
	case '#':
	    flags |= FORMAT_FLAG_ALT;
	    (*ppformat)++;
	    break;

	case '0':
	    /* We never store allocated storage under "pad", so we can
	       ignore any returned stale data. */
	    parser_data_set(pparser, "pad", SNV_UINT_TO_POINTER('0'), NULL);
	    (*ppformat)++;
	    break;

	case '-':
	    flags |= FORMAT_FLAG_LEFT;
	    (*ppformat)++;
	    break;
	    
	case ' ':
	    flags |= FORMAT_FLAG_SPACE;
	    (*ppformat)++;
	    break;

	case '+':
	    flags |= FORMAT_FLAG_SHOWSIGN;
	    (*ppformat)++;
	    break;

	case '\'':
	    flags |= FORMAT_FLAG_GROUP;
	    (*ppformat)++;
	    break;

	default:
	    pparser->state = ~(SNV_STATE_BEGIN|SNV_STATE_FLAG);
	    break;
	}
    }
	
    /* Store the accumulated flags in the parser struct. */
    parser_data_set(pparser, "flags", SNV_UINT_TO_POINTER(flags), NULL);

    /* Return the number of characters emitted. */
    return 0;
}

static
DEFINE_PRINTFV_HANDLER(number_handler)
{
    char *pEnd = NULL;
    char *key = NULL;
    unsigned long value; /* we use strtoul, and cast back to int. */

    return_val_if_fail(pparser != NULL, SNV_ERROR);

    /* If we are looking at a ``.'', then this is a precision parameter. */
    if (**ppformat == '.')
    {
	if (!(pparser->state & (SNV_STATE_PRECISION|SNV_STATE_BEGIN)))
	{
	    PARSER_ERROR(pparser, "invalid specifier");
	    return -1;
	}
	
	key = "prec";
	(*ppformat)++;

	/* Acceptable states for the next token. */
	pparser->state = SNV_STATE_MODIFIER|SNV_STATE_SPECIFIER;
    }

    /* Parse the number (or optionally a ``*''). */
    value = strtoul(*ppformat, &pEnd, 10);
    if (pEnd != NULL && pEnd > *ppformat)
    {
	*ppformat = pEnd;
    }
    else if (**ppformat == '*')
    {
	value = (unsigned long)INT_MIN;
	(*ppformat)++;

	/* If the caller wants type info we will have an address at which
	   to store that info. */
	if (pargtypes != NULL)
	{
	    snv_assert(pargindex != NULL);
	    printfv_argtype_renew(SNV_INT, pargtypes, pargc, *pargindex);
	    (*pargindex)++;
	}
    }
    else
    {
	PARSER_ERROR(pparser, "invalid specifier");
	return -1;
    }
    
    /* If the next character is a dollar,
       then the number is an argument index. */
    if (**ppformat == '$')
    {
	if (!(pparser->state & SNV_STATE_BEGIN))
	{
	    PARSER_ERROR(pparser, "invalid specifier");
	    return -1;
	}
	
	if (value == (unsigned long)INT_MIN)
	{
	    /* Reject ``*'' for an argument index. */
	    PARSER_ERROR(pparser, "invalid specifier");
	    return -1;
	}

	key = "dollar";
	value--;		/* argv indices are zero-based */
	    
	/* Acceptable states for the next token. */
	pparser->state = ~SNV_STATE_BEGIN;
	(*ppformat)++;
    }

    if (key == NULL)
    {
	/* Implies we must have read a width specification. */
	if (!(pparser->state & (SNV_STATE_BEGIN|SNV_STATE_WIDTH)))
	{
	    PARSER_ERROR(pparser, "invalid specifier");
	    return -1;
	}
	
	key = "width";
	
	/* Acceptable states for the next token. */
	pparser->state = ~(SNV_STATE_BEGIN|SNV_STATE_FLAG|SNV_STATE_WIDTH);
    }

    if ((value != (unsigned long)INT_MIN) && (value > INT_MAX))
    {
	PARSER_ERROR(pparser, "out of range");
	return -1;
    }
    
    parser_data_set(pparser, key, SNV_INT_TO_POINTER((int)value), NULL);
    
    return 0;
}

static
DEFINE_PRINTFV_HANDLER(modifier_handler)
{
    unsigned flags;

    return_val_if_fail(pparser != NULL, SNV_ERROR);

    /* Check for valid pre-state. */
    if (!(pparser->state & (SNV_STATE_BEGIN|SNV_STATE_MODIFIER)))
    {
	PARSER_ERROR(pparser, "out of range");
	return -1;
    }
    
    flags = fetch_zero_defaulted_uint(pparser, "flags");

    while (pparser->state != SNV_STATE_SPECIFIER)
    {
	switch (**ppformat)
	{
	case 'h':
	    flags |= SNV_FLAG_SHORT;
	    (*ppformat)++;
	    break;

	case 'l':
	    (*ppformat)++;
	    if (**ppformat != 'l')
	    {
		flags |= SNV_FLAG_LONG;
		break;
	    }
	    /*NOBREAK*/
	case 'q':
	case 'L':
	    flags |= SNV_FLAG_LONG_DOUBLE;
	    (*ppformat)++;
	    break;

	default:
	    pparser->state = SNV_STATE_SPECIFIER;
	    break;
	}
    }
    
    parser_data_set(pparser, "flags", SNV_UINT_TO_POINTER(flags), NULL);

    /* Return the number of characters emitted. */
    return 0;
}

static
DEFINE_PRINTFV_HANDLER(char_specifier_handler)
{
    int prec, width, count_or_errorcode = SNV_OK;
    unsigned flags, *pdollar;
    char ch = '\0';

    return_val_if_fail((pparser != NULL) && (pargc != NULL), SNV_ERROR);

    /* Check for valid pre-state. */
    if (!(pparser->state & (SNV_STATE_BEGIN|SNV_STATE_SPECIFIER)))
    {
	PARSER_ERROR(pparser, "out of range");
	return -1;
    }
    
    flags = fetch_zero_defaulted_uint(pparser, "flags");

    width = fetch_argv_defaulted_int(pparser, "width", argv, pargindex);
    prec  = fetch_argv_defaulted_int(pparser, "prec",  argv, pargindex);
    return_val_if_fail((width >= 0) && (prec >= 0), SNV_ERROR);

    /* Extract the correct argument from the arg vector. */
    pdollar = (unsigned*)parser_data_get(pparser, "dollar");
    if (argv != NULL)
    {
	if (pdollar != NULL)
	{
	    ch = (char)SNV_POINTER_TO_UINT(argv[*pdollar]);
	}
	else if (pargindex != NULL)
	{
	    ch = (char)SNV_POINTER_TO_UINT(argv[(*pargindex)++]);
	}
    }
    
    /* Advance the format pointer past the parsed character. */
    (*ppformat)++;
    
    /* Left pad to the width if the supplied argument is less than
     * the width specifier.
     */
    if ((width > 1) && (ISSET(flags, FORMAT_FLAG_LEFT)))
    {
	char pad          = fetch_space_defaulted_char(pparser, "pad");
	unsigned padwidth = width - 1;

	while ((count_or_errorcode >= 0)
	       && (count_or_errorcode < padwidth))
	{
	    EMIT(pad, stream, count_or_errorcode);
	}
    }

    /* Emit the character argument.
     */
    EMIT(ch, stream, count_or_errorcode);

    /* Right pad to the width if we still didn't reach the specified
     * width and the left justify flag was set.
     */
    if ((count_or_errorcode < width) && (ISSET(flags, FORMAT_FLAG_LEFT)))
    {
	while ((count_or_errorcode >= 0) && (count_or_errorcode < width))
	{
	    /* we always left justify with spaces */
	    EMIT(' ', stream, count_or_errorcode);
	}
    }

    /* If the caller wants type info we will have an address at which
       to store that info. */
    if (pargtypes != NULL)
    {
	if (pdollar != NULL)
	{
	    printfv_argtype_renew(SNV_CHAR, pargtypes, pargc, *pdollar);
	}
	else
	{
	    printfv_argtype_renew(SNV_CHAR, pargtypes, pargc, *pargindex);
	    (*pargindex)++;
	}
    }

    /* Set post-state. */
    pparser->state = SNV_STATE_END;

    /* Return the number of characters emitted. */
    return count_or_errorcode;
}

static int
format_number (pparser, stream, ppformat, argv, pargc,
	       pargtypes, pargindex, base)
    printfv_parser * const pparser;
    STREAM *stream;
    const char **ppformat;
    snv_constpointer const *argv;
    int *pargc, **pargtypes, *pargindex;
    unsigned base;
{
    static const char digits_lower[] = "0123456789abcdefghijklmnopqrstuvxyz";
    static const char digits_upper[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVXYZ";
    const char *digits;

    unsigned long value = 0L;
    int specifier = (int)**ppformat;
    int width, prec, type = SNV_INT, count_or_errorcode = SNV_OK;
    unsigned flags, *pdollar;
    char pad, buffer[256], *p, *end;
    boolean is_negative;

    return_val_if_fail(pparser != NULL, SNV_ERROR);

    /* Check for valid pre-state. */
    if (!(pparser->state & (SNV_STATE_BEGIN|SNV_STATE_SPECIFIER)))
    {
	PARSER_ERROR(pparser, "out of range");
	return -1;
    }
    
    /* Upper or lower-case hex conversion? */
    digits = ((specifier >= 'a') && (specifier <= 'z'))
	   ? digits_lower : digits_upper;

    flags = fetch_zero_defaulted_uint(pparser, "flags");

    width = fetch_argv_defaulted_int(pparser, "width", argv, pargindex);
    prec  = fetch_argv_defaulted_int(pparser, "prec",  argv, pargindex);
    pad   = fetch_space_defaulted_char(pparser, "pad");
    return_val_if_fail((width >= 0) && (prec >= 0), SNV_ERROR);

    /* Ensure that the type flags are propogated to the caller. */
    if (**ppformat == 'u')
    {
	type |= SNV_FLAG_UNSIGNED;
    }
    if (ISSET(flags, SNV_FLAG_LONG))
    {
	type |= SNV_FLAG_LONG;
    }

    /* Extract the correct argument from the arg vector. */
    pdollar = (unsigned*)parser_data_get(pparser, "dollar");
    if (argv != NULL)
    {
	if (**ppformat == 'u')
	{
	    if (pdollar != NULL)
	    {
		value = fetch_argv_ulong(pparser, argv, *pdollar);
	    }
	    else if (pargindex != NULL)
	    {
		value = fetch_argv_ulong(pparser, argv, *pargindex);
		(*pargindex)++;
	    }
	    is_negative = FALSE;
	}
	else
	{
	    long svalue;
	    if (pdollar != NULL)
	    {
		svalue = fetch_argv_long(pparser, argv, *pdollar);
	    }
	    else if (pargindex != NULL)
	    {
		svalue = fetch_argv_long(pparser, argv, *pargindex);
		(*pargindex)++;
	    }
	    is_negative = (svalue < 0);
	    value = (unsigned long)ABS(svalue);
	}
    }
    
    /* Advance the format pointer past the parsed character. */
    (*ppformat)++;
    
    /* Convert the number into a string. */
    p = end = &buffer[sizeof(buffer) - 1];

    if (value == 0)
    {
	*p-- = '0';
    }
    else while (value > 0)
    {
	*p-- = digits[value % base];
	value /= base;
    }
    width -= end - p;
    prec  -= end - p;

    /* Octal numbers have a leading zero in alterate form. */
    if (ISSET(flags, FORMAT_FLAG_ALT)
	&& base == 8 && prec <= 0 && *p != '0')
    {
	*p-- = '0';
	--width;
    }

    /* Left pad with zeros to make up the precision. */
    if (prec > 0)
    {
	width -= prec;
	while (prec-- > 0)
	{
	    *p-- = '0';
	}
    }
    
    /* Reserve room for leading `0x' for hexadecimal. */
    if (ISSET(flags, FORMAT_FLAG_ALT) && base == 16)
    {
	width -= 2;
    }
    
    /* Reserve room for a sign character. */
    if (is_negative || ISSET(flags, FORMAT_FLAG_ALT|FORMAT_FLAG_SPACE))
    {
	--width;
    }

    /* Left pad to the remaining width if the supplied argument is less
     * than the width specifier, and the padding character is ' '.
     */
    if (pad == ' ' && !ISSET(flags, FORMAT_FLAG_LEFT))
    {
	while ((count_or_errorcode >= 0)
	       && (width-- > 0))
	{
	    EMIT(pad, stream, count_or_errorcode);
	}
    }

    /* Display any sign character. */
    if (count_or_errorcode >= 0)
    {
	if (is_negative)
	{
	    EMIT('-', stream, count_or_errorcode);
	}
	else if (ISSET(flags, FORMAT_FLAG_SHOWSIGN))
	{
	    EMIT('+', stream, count_or_errorcode);
	}
	else if (ISSET(flags, FORMAT_FLAG_SPACE))
	{
	    EMIT(' ', stream, count_or_errorcode);
	}
    }

    /* Left pad to the remaining width if the supplied argument is less
     * than the width specifier, and the padding character is not ' '.
     */
    if (pad != ' ' && !ISSET(flags, FORMAT_FLAG_LEFT))
    {
	while ((count_or_errorcode >= 0)
	       && (width-- > 0))
	{
	    EMIT(pad, stream, count_or_errorcode);
	}
    }

    /* Display `0x' for alternate hexadecimal specifier. */
    if ((count_or_errorcode >= 0) && (base == 16)
	&& ISSET(flags, FORMAT_FLAG_ALT))
    {
	EMIT('0', stream, count_or_errorcode);
	EMIT(specifier, stream, count_or_errorcode);
    }

    /* Fill the stream buffer with as many characters from the number
     * buffer as possible without overflowing.
     */
    while ((count_or_errorcode >= 0) && (++p < &buffer[sizeof(buffer)]))
    {
	EMIT(*p, stream, count_or_errorcode);
    }

    /* Right pad to the width if we still didn't reach the specified
     * width and the left justify flag was set.
     */
    if (ISSET(flags, FORMAT_FLAG_LEFT))
    {
	while ((count_or_errorcode >= 0) && (width-- > 0))
	{
	    /* we always left justify with spaces */
	    EMIT(' ', stream, count_or_errorcode);
	}
    }

    /* If the caller wants type info we will have an address at which
       to store that info. */
    if (pargtypes != NULL)
    {
	if (pdollar != NULL)
	{
	    printfv_argtype_renew(type, pargtypes, pargc, *pdollar);
	}
	else
	{
	    printfv_argtype_renew(type, pargtypes, pargc, *pargindex);
	    (*pargindex)++;
	}
    }

    /* Set post-state. */
    pparser->state = SNV_STATE_END;

    /* Return the number of characters emitted. */
    return count_or_errorcode;
}

static
DEFINE_PRINTFV_HANDLER(decimal_specifier_handler)
{
    return format_number (pparser, stream, ppformat,
			  argv, pargc, pargtypes, pargindex, 10);
}

static
DEFINE_PRINTFV_HANDLER(hex_specifier_handler)
{
    return format_number (pparser, stream, ppformat,
			  argv, pargc, pargtypes, pargindex, 16);
}

static
DEFINE_PRINTFV_HANDLER(octal_specifier_handler)
{
    return format_number (pparser, stream, ppformat,
			  argv, pargc, pargtypes, pargindex, 8);
}

static
DEFINE_PRINTFV_HANDLER(string_specifier_handler)
{
    int prec, width, count_or_errorcode = SNV_OK;
    unsigned flags, len = 0, *pdollar;
    char *p = NULL;

    return_val_if_fail(pparser != NULL, SNV_ERROR);

    /* Check for valid pre-state. */
    if (!(pparser->state & (SNV_STATE_BEGIN|SNV_STATE_SPECIFIER)))
    {
	PARSER_ERROR(pparser, "out of range");
	return -1;
    }

    flags = fetch_zero_defaulted_uint(pparser, "flags");

    width = fetch_argv_defaulted_int(pparser, "width", argv, pargindex);
    prec  = fetch_argv_defaulted_int(pparser, "prec",  argv, pargindex);
    return_val_if_fail((width >= 0) && (prec >= 0), SNV_ERROR);

    /* Extract the correct argument from the arg vector. */
    pdollar = (unsigned*)parser_data_get(pparser, "dollar");
    if (argv != NULL)
    {
	if (pdollar != NULL)
	{
	    p = (char*)argv[*pdollar];
	}
	else if (pargindex != NULL)
	{
	    p = (char*)argv[(*pargindex)++];
	}
    }
    
    /* Advance the format pointer past the parsed character. */
    (*ppformat)++;

    /* Left pad to the width if the supplied argument is less than
     * the width specifier.
     */
    if (p != NULL)
    {
	len = prec ? MIN(strlen(p), prec) : strlen(p);
    }
    
    if ((len < width) && (!ISSET(flags, FORMAT_FLAG_LEFT)))
    {
	unsigned padwidth = width - len;
	while ((count_or_errorcode >= 0)
	       && (count_or_errorcode < padwidth))
	{
	    char pad      = fetch_space_defaulted_char(pparser, "pad");
	    EMIT(pad, stream, count_or_errorcode);
	}
    }

    /* Fill the buffer with as many characters from the format argument
     * as possible without overflowing or exceeding the precision.
     */
    if ((count_or_errorcode >= 0) && (p != NULL))
    {
	int mark = count_or_errorcode;
	while ((count_or_errorcode >= 0) && *p != '\0'
	       && ((prec == 0) || (count_or_errorcode - mark < len)))
	{
	    EMIT(*p++, stream, count_or_errorcode);
	}
    }

    /* Right pad to the width if we still didn't reach the specified
     * width and the left justify flag was set.
     */
    if ((count_or_errorcode < width) && ISSET(flags, FORMAT_FLAG_LEFT))
    {
	while ((count_or_errorcode >= 0) && (count_or_errorcode < width))
	{
	    /* we always left justify with spaces */
	    EMIT(' ', stream, count_or_errorcode);
	}
    }

    /* If the caller wants type info we will have an address at which
       to store that info. */
    if (pargtypes != NULL)
    {
	if (pdollar != NULL)
	{
	    printfv_argtype_renew(SNV_STRING, pargtypes, pargc, *pdollar);
	}
	else
	{
	    printfv_argtype_renew(SNV_STRING, pargtypes, pargc, *pargindex);
	    (*pargindex)++;
	}
    }

    /* Set post-state. */
    pparser->state = SNV_STATE_END;

    /* Return the number of characters emitted. */
    return count_or_errorcode;
}

static
DEFINE_PRINTFV_HANDLER(unsigned_specifier_handler)
{
    return format_number (pparser, stream, ppformat,
			  argv, pargc, pargtypes, pargindex, 10);
}

/* format.c ends here */
