/****************************************************************************/
/*	Copyright (c) 2009, Bendor Research Pty. Ltd. All rights reserved.		*/
/*	This software is released under the GNU General Public License, version	*/
/*	3, with the exemptions that 1) the header files installed by this 	 	*/
/*	package can be included in closed-source code and 2) the libraries		*/
/*	built by this package can be statically linked into a closed-source 	*/
/*	binary. See the file LICENSE for the details.							*/
/*	This software is provided "AS IS", without any warranty or guarantee.	*/
/*	By using this software you acknowledge that neither Bendor Research nor	*/
/*	any of its personnel or affiliates can be held liable for any damages,	*/
/*	including, but not limited to, loss of business, loss of money, loss of */
/*	life, loss or reputation, arising out of this software being faulty or	*/
/*	not performing as advertised, even if Bendor Research was aware of such	*/
/*	faults in the software. You use this software at your own risk, any and */
/*	all liabilities that arise out of your using this software are strictly	*/
/*	yours.																	*/
/****************************************************************************/

#include <blc_stdio.h>

/****************************************************************************/
/*					Local prototypes										*/
/****************************************************************************/

static	int		Float( SCANF *stat );
static	int		Chars( SCANF *stat );

/****************************************************************************/
/*					API functions											*/
/****************************************************************************/

/** @addtogroup stdio */
/**	@{ */

/**
*	Formatted input core.
*
*	This function implements the core of the formatted input standard C
*	library function set (the scanf() family functions). The library itself
*	does not provide those functions as such, since your input/output
*	configuration is unknown to the library. However, using this function
*	you can implement those functions in only a few lines of C code, as
*	shown by the examples below.
*
*	@param	fmt		The format string.
*	@param	arg		The argument list (after the format string). Before you
*					call this function, you have to call va_start() and pass
*					the resulting va_list object in this argument.
*	@param	get		The function that reads the next character from the input.
*	@param	ung		The function to call when a character must be pushed back.
*					This function is called at most once.
*	@param	par		This is the parameter that is passed to \p get and \p ung.
*	@param	m2w		Pointer to the mbtowci() function. Since the size of the
*					wchar_t type used by the application can not be known
*					when the library is compiled, functions that manipulate
*					memory storing wchar_t objects must be passed in invocation
*					time, hence this parameter. If this argument is NULL, then
*					the 'l' size modifier in the 'c', 's' and '[' conversions
*					will be ignored.
*
*	@return			The number of specifiers successfully matched. The
*					<b>\%n</b> specifier is <i>not</i> included in the count.
*					EOF is returned if the input was exhausted or there
*					was a character mismatch before the first conversion,
*					0 is returned if the first conversion fails and a negative
*					error code, other than EOF, is returned if the <i>get</i>
*					function reported a read error from the input.
*
*	@par Format string details:
*	The function reads the format string and tries to match it with the
*	input. A whitespace character in the format string matches any number
*	of whitespace characters on the input, including none. Any character
*	other than whitespace or the '\%' character in the format string must
*	match the input exactly; if a mismatch occurs, the function returns.
*	If the formt string contains a '\%' character, it is then taken as a
*	conversion specificator. If its format is incorrect, then it is treated as
*	a mismatch, otherwise the input is converted according to the specificator
*	and the result is stored by the location specified by the next argument
*	of the variable argument list.
*	The format of the conversion specificator is the following:
*	<tt><br><br>
*	%[sup][width][size]specifier
*	</tt><br><br>
*	Apart from the conversion specifier all other fields are optional.
*	The details of the various fields are as follows:
*	<br><br><b>
*	Suppression
*	</b><br>
*	If the first character after the '\%' is a '*', then the conversion
*	will be performed, but the result will be discarded. No pointer will
*	be consumed from the variable argument list (but if dynamic field with
*	is specified, then the corresponding parameter <i>will</i> be consumed)
*	and the conversion count will not be incremented.
*	<br><br><b>
*	Field width
*	</b><br>
*	It is either a sequence of decimal digits or the single character of '$'.
*	<i>Note that the '$' character is a non-standard extension.</i>
*	A decimal digit sequence is interpreted as a decimal number. The '$'
*	character will fetch the next \p int argument from the argument list and
*	use that as the width; if the value is negative, it will be taken as unset.
*	<br><br><b>
*	Size modifier
*	</b><br>
*	The interpretation of the next argument can be affected by the size
*	modifierr. The exact interpretation of the length modifier depends on the
*	conversion and is detailed for each conversion specifier separately. The
*	following length modifiers are defined:
*			<dl><dt><tt>
*	"hh"	</tt></dt><dd>
*			Indicates \p char or \p unsigned \p char argument.
*			<br><br></dd><dt><tt>
*	"h"		</tt></dt><dd>
*			Indicates \p short or \p unsigned \p short argument.
*			<br><br></dd><dt><tt>
*	"l"		</tt></dt><dd>
*			Indicates \p double, \p long, \p unsigned \p long or
*			\p wchar_t argument.
*			<br><br></dd><dt><tt>
*	"ll"	</tt></dt><dd>
*			Indicates <tt>long double, long long</tt> or <tt>
*			unsigned long long</tt> argument.<br>
*			<br><br></dd><dt><tt>
*	"L"		</tt></dt><dd>
*			Identical to \p ll.
*			<br><br></dd><dt><tt>
*	"j"		</tt></dt><dd>
*			Indicates \p intmax_t \p argument.<br>
*			<i>This implementation treats it as if it were an "ll".</i>
*			<br><br></dd><dt><tt>
*	"t"		</tt></dt><dd>
*			Indicates \p ptrdiff_t \p argument.<br>
*			<i>This implementation treats it as if it were an "l".</i>
*			<br><br></dd><dt><tt>
*	"z"		</tt></dt><dd>
*			Indicates \p size_t \p argument.<br>
*			<i>This implementation treats it as if it were an "l".</i>
*			<br><br></dd></dl>
*	<br><br><b>
*	Conversion specifiers
*	</b><br>
*	This single character field is compulsory. If it is missing, then the
*	function will return as if a conversion mismatch happened.
*			<dl><dt><tt>
*	'd'		</tt></dt><dd>
*			Converts a possibly signed decimal integer.
*			The conversion is according to the semantics of the strtol() or
*			strtoll() functions with radix 10.
*			Before the conversion, leading whitespace is discarded.
*			If field width is specified, then not more than that many
*			characters are consumed from the input. The leading whitespace
*			is not counted in the field width but the signum is.
*			If the field width is set to 0, then the conversion will fail.
*			The next argument must be a pointer to an \p int, or to a \p char
*			if the 'hh' size modifier was present, or to a \p short if the 'h'
*			size modifier was present, or to a \p long if the 'l' or
*			equivalent size modifier was present, or to a \p long \p long if
*			the 'll' or equivalent modifier was present.
*			<br><br></dd><dt><tt>
*	'i'		</tt></dt><dd>
*			Converts a possibly signed integer. The conversion is
*			according to the semantics of the strtol() or strtoll() functions
*			with radix 0, that is, if the input starts with 0x or 0X, then
*			hexadecimal conversion takes place, otherwise if it starts with 0
*			then octal conversion is performed, otherwise decimal conversion
*			is performed.
*			With regards to the field width and leading whitespace, the
*			behaviour of this conversion is identical to the 'd' conversion.
*			The next argument must be a pointer to an (appropriately sized)
*			\p int.
*			<br><br></dd><dt><tt>
*	'u'		</tt></dt><dd>
*			Converts an unsigned decimal integer. The conversion is
*			according to the semantics of the strtoul() or strtoull() functions
*			with radix 10.
*			With regards to the field width and leading whitespace, the
*			behaviour of this conversion is identical to the 'd' conversion.
*			The next argument must be a pointer to an (appropriately sized)
*			\p unsigned \p int.
*			<br><br></dd><dt><tt>
*	'o'		</tt></dt><dd>
*			Converts an unsigned octal integer. The conversion is according
*			to the semantics of the strtoul() or strtoull() functions
*			with radix 8.
*			With regards to the field width and leading whitespace, the
*			behaviour of this conversion is identical to the 'd' conversion.
*			The next argument must be a pointer to an (appropriately sized)
*			\p unsigned \p int.
*			<br><br></dd><dt><tt>
*	'x'		</tt><tt>
*	'X'		</tt></dt><dd>
*			Converts an unsigned octal integer. The conversion is according
*			to the semantics of the strtoul() or strtoull() functions
*			with radix 16.
*			With regards to the field width and leading whitespace, the
*			behaviour of this conversion is identical to the 'd' conversion.
*			The next argument must be a pointer to an (appropriately sized)
*			\p unsigned \p int.
*			<br><br></dd><dt><tt>
*	'p'		</tt></dt><dd>
*			This conversion is identical to the 'x' conversion, with two
*			exceptions. First, the size qualifiers are ignored and the next
*			argument must be a pointer to a pointer.
*			Second, if after discarding the leading whitespace the input
*			contains the string <tt>(nil)</tt>, then it is accepted and
*			is interpreted as 0 (i.e. NULL pointer).
*			<br><br></dd><dt><tt>
*	'n'		</tt></dt><dd>
*			Fetches the next \p int \p * argument and stores the number of
*			characters consumed from the input at the given location.
*			The "ll", "l", "h", "hh" or equivalent size modifiers,
*			if present, are respected and the pointer is typecast to a pointer
*			to, respectively, <tt>long long, long, short</tt> or \p char
*			when storing the number. This conversion does not consume any
*			input, does not increase the conversion count and can not fail.
*			If assignment supression is also specified, then this conversion
*			is a null operation, consuming neither the input nor the arguments.
*			<br><br></dd><dt><tt>
*	'a'		</tt><tt>
*	'e'		</tt><tt>
*	'f'		</tt><tt>
*	'g'		</tt></dt><dd>
*			Converts a float-point number. The conversion semantics is the
*			same as that of the strtod() function. Leading whitespace and
*			field width is treated as in the case of the 'd' conversion.
*			The next argument must be a pointer to a \p float, or if the "l"
*			(or equivalent) size modifier was present, then to a
*			\p double or if the "ll" (or equivalent) size modifier was
*			present, to a \p long \p double. Note that the implementation
*			always performs a string to \p double conversion and the result
*			is then typecast to \p float or \p long \p double.
*			<br><br></dd><dt><tt>
*	'c'		</tt></dt><dd>
*			Leading whitespace is <b>not</b> skipped. If the field width
*			is not specified, then it is set to 1. If the field width is
*			explicitely set to 0, then the conversion will succeed without
*			consuming input or producing output.
*			If no 'l' size modifier was specified, then field width number
*			of characters are consumed from the input and stored at the
*			location pointed by the next argument, which must pe a pointer
*			to \p char. No terminating NUL is added to the string. If the
*			input is exhausted before the given number of characters are
*			processed, then the conversion will stop but otherwise succeed,
*			except if not even one character could be processed.<br>
*			If the "l" (or equivalent) size modifier was specified, then
*			the input is processed as UTF-8 encoded wide characters and the
*			result is stored at the location pointed by the next argument
*			that must be a pointer to <tt>wchar_t</tt>. The conversion of
*			the input is according to the semantics of the mbtowc() function.
*			Note that the field length is the <i>number of wide characters</i>
*			and <b>not</b> the number of bytes used from the input. If at any
*			point the UTF-8 to <tt>wchar_t</tt> conversion signals an error,
*			a conversion mismatch is declared and the function returns.<br>
*			Note that encountering a 0 on the input does not stop the
*			conversion. Also note that the resulting character or wide
*			character string is <b>not</b> terminated with a 0. If the input
*			gets exhausted at a multibyte sequence boundary, then it is treated
*			the same as without the 'l' modifier, if the input gets
*			exhausted inside a multibyte sequence, then it is considered a
*			conversion error.<br>
*			Any length modifier other than "l" (or equivalent) is ignored.
*			<br><br></dd><dt><tt>
*	's'		</tt></dt><dd>
*			Any leading whitespace on the input is skipped.
*			If no "l" length modifier is specified, then the next argument must
*			be a pointer to char, if 'l' was specified, it must be a pointer to
*			wchar_t.
*			Characters or multibyte character sequences are read from the
*			input and the result is stored at the location pointed by the
*			argument pointer. The conversion stops if either field width number
*			of characters or wide characters have been processed or if a
*			whitespace character is read from the input. The whitespace will
*			not be stored and will not be consumed. The stored strin <b>is</b>
*			terminated with a 0 character (or wide character). The terminator
*			charatcer is not counted in the field width and it is omitted if
*			the conversion fails. A conversion with a field width of 0
*			matches without consuming input, but it still consumes the
*			argument stores the terminating 0 in the result.
*			The exhaustion of the input is treated exactly as with the
*			'c' conversion. An illegal UTF-8 sequence will cause a conversion
*			failure. <br>
*			Note that if the 'l' modifier is present, the testing for whitespace
*			is done before the UTF-8 to wide char conversion is done.
*			This means that an overlong multibyte sequence encoding a blank
*			character will not be recognised as whitespace. Also note that
*			that is only an issue if the UTF-8 input violates the rules set
*			forth by RFC3629.
*			<br><br></dd><dt><tt>
*	'['		</tt></dt><dd>
*			Leading whitespace is <b>not</b> skipped. If field width is
*			not specified, it is set to 1. Field width of 0 matches without
*			consuming input or producing output, but it consumes an argument.
*			Input is read and treated as described with the 'c' conversion.
*			However, the conversion stops if the next input character does not
*			match the set, as described below.
*			The set of characters are the characters between the '[' and a
*			closing ']'. Thus, [abc] is the set of a, b and c. If the ']'
*			character itself must be in the set, it must be the first character
*			of the set (otherwise it is interpreted as the end of set marker.
*			Thus, [][] is a set of '[' and ']'.
*			The set can contain ranges. A range is two characters with
*			'-' between them. [0-9] describes the decimal digits. Therefore,
*			if you want '-' be in your set it must be last character within
*			the set. [0-9.eE+-] is the set of characters that a decimal float
*			point number can possibly contain.
*			The interpretation of the set is inverted if the first character
*			after the '[' is a '^'. The input will match any character not
*			in the set. If you need the ']' in the excluded set, then it must
*			be the character following the '^'. That is, [^]^[-] matches
*			anything but [, ], ^ and -.
*			When the set is processed, characters between the '[' and ']'
*			are treated as bytes, therefore any value between 1 and 255 can
*			be used.<br>
*			The interpretation of the set is slightly changed if you use the
*			'l' size modifier. The following restrictions apply:
*			- 	if you use a non-ASCII character in the set, that is, a byte
*				with a value over 127, it will be accepted, but will otherwise
*				be ignored.
*			-	any multibyte character sequence longer than 1 byte is
*				considered as *not* being in the set, even if the sequence
*				is an overlong one that describes an ASCII character. If you
*				specified the set with inverted interpretation, then multibyte
*				characters longer than 1 byte are accepted without any further
*				checking.
*			This means that if you specify [\\x01-\\x7f] it will accept any
*			single-byte sequence but reject any longe ones, while specifying
*			[^\\x01-\\x7f] will reject any single-byte sequence but accept all
*			longer ones. If you use [\\x80-\\xff] then no character whatsoever
*			will be accepted, because the set is composed of character codes
*			above 127, thus will never match a single-byte UTF-8 sequence and
*			longer UTF-8 sequences are rejected automatically, since the set
*			is a positive one.
*			<br><br></dd></dl>
*	@par Usage example
*	@include scanf.c
*/

int		scan_flt( const char *fmt, va_list arg, BLC_FMT_GET get,
				  BLC_FMT_UNG ung, void **par,
				  int(*m2w)(wchar_t**,const char*,size_t) )
{
SCANF stat;

	stat.form = (unsigned char *) fmt;
	stat.cbuf = EOF;
	stat.args = arg;
	stat.fget = get;
	stat.fung = ung;
	stat.parm = par;
	stat.real = Float;
	stat.chrs = Chars;
	stat.mtow = m2w;
	return( _blc_scan( &stat ) );
}

/** @} */

/****************************************************************************/
/*					Local functions											*/
/****************************************************************************/

/*
*	Conversion of a float-point value
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		Float( SCANF *stat )
{
BLC_S2F		conv;
void		*ptr;
int			i, c, r;

	if ( ! _blc_scan_space( stat ) ) return( 0 );
	ptr = stat->supp ? NULL : va_arg( stat->args, char * );

	if ( ! stat->fldw ) return( 0 );
	if ( stat->fldw < 0 ) stat->fldw = ( 1u << 31 ) - 1;

	_blc_StrToFlt( &conv );

	for ( i = stat->fldw ; i ; i-- ) {

		c = _blc_scan_get( stat );
		r = (*conv.func)( &conv, c );

		if ( r < 0 ) {

			// Conversion failed

			return( 0 );
		}

		if ( r > 0 ) {

			// conversion finished

			_blc_scan_ung( stat, c );
			break;
		}
	}

	if ( ! i && (*conv.func)( &conv, 0 ) < 0 ) return( 0 );

	if ( ptr ) {

		switch ( stat->size ) {

			case SIZE_HUGE:

				*((long double *) ptr) = conv.data.numb;
				break;

			case SIZE_LONG:

				*((double *) ptr) = conv.data.numb;
				break;

			default:

				*((float *) ptr) = conv.data.numb;
				break;
		}

		stat->conv++;
	}

	return( 1 );
}

/*
*	Conversion of a sequence of (possibly wide) characters
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static int	Chars( SCANF *stat )
{
wchar_t		*ptr;
int			chr, num, cnt, eof;
unsigned	set[ 8 ];
char		tmp[ 6 ];

	if ( ! stat->mtow || stat->size != SIZE_LONG )

		return( _blc_scan_chars( stat ) );

	ptr = stat->supp ? NULL : va_arg( stat->args, void * );

	// Process field width and leading whitespace and whatever
	// else is needed.

	switch ( stat->type ) {

		case 'c':

			if ( stat->fldw < 0 ) stat->fldw = 1;
			break;

		case '[':

			if ( stat->fldw < 0 ) stat->fldw = 1;
			if ( ! _blc_scan_cset( stat, set ) ) return( 0 );
			break;

		case 's':

			if ( stat->fldw < 0 ) stat->fldw = ( 1u << 31 ) - 1;
			if ( ! _blc_scan_space( stat ) ) return( 0 );
	}

	for ( eof = 1 ; stat->fldw-- ; eof = 0 ) {

		// Fetch the first byte of the sequence and see how many
		// bytes do we need for the conversion

		chr = _blc_scan_get( stat );

		if ( chr == EOF ) {

			// Input was exhausted, the conversion is successful if we've
			// processed at least one character

			if ( eof )

				return( 0 );
			else
				break;
		}

		if ( chr < 0 ) return( 0 );

		tmp[ 0 ] = chr;

		if ( ( chr & 0x80 ) == 0x00 ) {

			// It is a single character sequence. Test if it is still
			// acceptable according to the conversion's criteria.

			switch ( stat->type ) {

				case 'c':

						if ( ptr ) (*stat->mtow)( &ptr, tmp, 1 );
						continue;

				case 's':

					if ( ! isspace( chr ) ) {

						if ( ptr ) (*stat->mtow)( &ptr, tmp, 1 );
						continue;
					}

					break;

				case '[':

					if ( ( set[ INDX( chr ) ] & BPOS( chr ) ) ) {

						if ( ptr ) (*stat->mtow)( &ptr, tmp, 1 );
						continue;
					}

					break;

				case ']':

					if ( ! ( set[ INDX( chr ) ] & BPOS( chr ) ) ) {

						if ( ptr ) (*stat->mtow)( &ptr, tmp, 1 );
						continue;
					}

					break;
			}

			// The character failed the test, push it back
			// and report completion.

			_blc_scan_ung( stat, chr );
			break;
		}
		else {

			// A multibyte sequence

			if ( stat->type == '[' ) {

				// Test for being in a set. By definition, a multi-byte
				// character is not included in the set. Push the char
				// back and report completion

				_blc_scan_ung( stat, chr );
				break;
			}

			// Work out the length of the sequence

			num = 0;
			if ( ( chr & 0xe0 ) == 0xc0 ) num = 2;
			else if ( ( chr & 0xf0 ) == 0xe0 ) num = 3;
			else if ( ( chr & 0xf8 ) == 0xf0 ) num = 4;
			else if ( ( chr & 0xfc ) == 0xf8 ) num = 5;
			else if ( ( chr & 0xfe ) == 0xfc ) num = 6;

			if ( ! num ) {

				// This is not a valid start character.
				// This is a conversion failure.

				_blc_scan_ung( stat, chr );
				return( 0 );
			}

			// Get as many characters as needed

			for ( cnt = 1 ; cnt <= num ; cnt++ ) {

				if ( ( chr = _blc_scan_get( stat ) ) < 0 ) return( 0 );

				if ( ( chr & 0xc0 ) != 0x80 ) {

					// UTF-8 sequence violation

					_blc_scan_ung( stat, chr );
					return( 0 );
				}

				tmp[ cnt ] = chr;
			}

			chr = (*stat->mtow)( ptr ? &ptr : NULL, tmp, num );

			if ( chr < 0 ) {

				// The sequence was valid but it describes a wide char
				// that is wider than allowed by the actual wchar_t or
				// it breaks some RFC restrictions, so it's a failure.
				// Unfortunately, we can't push it all back.

				return( 0 );
			}
		}
	}

	// The conversion was successful. If the conversion was 's', then
	// we must terminate the wide char buffer.

	if ( ptr ) {

		if ( stat->type == 's' ) {

			tmp[ 0 ] = 0;
			(*stat->mtow)( &ptr, tmp, 1 );
		}

		stat->conv++;
	}

	return( 1 );
}

