//-----------------------------------------------------------------------------
// Copyright © 2003 - Philip Howard - All rights reserved
//
// 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; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//-----------------------------------------------------------------------------
// package	libh/io
// homepage	http://libh.slashusr.org/
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	libh at ipal dot org
// homepage	http://phil.ipal.org/
//-----------------------------------------------------------------------------
// This file is best viewed using a fixed spaced font such as Courier
// and in a display at least 120 columns wide.
//-----------------------------------------------------------------------------

#include "io_lib.h"

#define GET_ITEM_DEFAULT_OPTIONS	"\\\"'#"

//-----------------------------------------------------------------------------
// file		get_item.c
//
// purpose	Implement methods for reading parsed config/data items from an
//		input file or character array.
//-----------------------------------------------------------------------------
// functions		The following functions are implemented:
//
// get_item_new_file	Create a new get_item instance to read from a file.
// get_item_new_string	Create a new get_item instance to read from a string.
// get_item_new_call	Create a new get_item instance to read from a function.
// get_item_set_options	Specify a replacement set of options.
// get_item_add_options	Specify a set of options to enable.
// get_item_del_options	Specify a set of options to disable.
// get_item_set_hold	Set the hold-at-EOL mode.
// get_item_unset_hold	Unset the hold-at-EOL mode.
// get_item_next	Get next item from input and copy to target
// get_item_flush_line	Flush the remainder of the current line
// get_item_actual_len	Get the actual data length despite any truncation
// get_item_stored_len	Get the stored data length as truncated
// get_item_begin_cnt	Get the item beginning byte/octet position.
// get_item_begin_col	Get the item beginning column position.
// get_item_begin_row	Get the item beginning row position.
// get_item_end_cnt	Get the item ending byte/octet position.
// get_item_end_col	Get the item ending column position.
// get_item_end_row	Get the item ending row position.
// get_item_destroy	Destroy the get_item instance.
//-----------------------------------------------------------------------------
// features	The following features are supported:
//
// position	Functions are available to get the position of the item just
//		obtained.  The character position from the beginning of the
//		input data source (origin 0), the column number (origin 0) in
//		the row/line, and the row/line number (origin 0).
//
// newlines	A newline is a sequence of any of the following characters:
//			'\n' (newline)
//			'\r' (carriage return)
//			'\f' (form feed)
//			'\v' (vertical tab)
//		where each character code appears not more than one time, but
//		may appear in any order.  If any one of these characters is
//		encountered a second time, then it represents the beginning of
//		a second newline sequence, and hence an empty line in between.
//
// optional	The following features are optional and are selected via the
// features	get_item_set_options() or get_item_add_options() functions.
//
// equals	If the option string contains an equal character or a colon
// colons	character, then any equal or colon not in a quoted string will
//		be returned as separate data items.  The facilitates items
//		which represent name:value or name=value pairs.  They will
//		be returned as 3 items in order in 3 calls.
//
//		Default for equals: off
//		Default for colon: off
//
// quotes	If the option string contains a single quote, double quote,
//		or a backwards quote (often referred to as a "back tick"),
//		then quote characters of that type will enclose a quoted
//		string.  A quoted string differs from an unquoted string by
//		keeping spaces and tabs as is.  If a quoted string contains
//		a quote character of another type, it is simply part of the
//		string and stored as data.  If there is no space between a
//		quoted string and other data (quoted or not) then these
//		strings are merged as a single data item.
//
//		Default for single quote: on
//		Default for double quote: on
//		Default for back quote: off
//
// backslash	If the option string contains a backslash character, then the
//		backslash character is interpreted in one of the following ways
//		depending on what characters follow it:
//
//		A backslash character followed by a letter code in upper or
//		lower case is converted with the same meaning as in the C
//		programming language.  These characters are: a b e f n r t v z
//
//		A backslash character followed by 'x' followed by 1 or 2
//		cetal/hexadecimal characters in upper or lower case is
//		converted to the character code which is represented by the
//		cetal/hexadecimal value.
//
//		A backslash character followed by 1 to 3 characters in the
//		range of '0' to '7' is converted to the character code which
//		is represented by the octal value, modulo 256 (values in the
//		range 256 to 511 may be coded, but will be shifted into the
//		range 0 to 255).
//
//		A backslash followed by a newline sequence causes them both
//		to vanish effectively continuing the current line onto the
//		next line to form a larger logical line.  Data items are
//		broken between lines, so this cannot be used to continue a
//		long data item.  This behaviour may be changed in the future
//		so it is best not to depend on this always breaking data.
//
//		A backslash followed by a backslash will result in a single
//		backslash character.
//
//		A backslash followed by any other character will not result
//		in any data, but the character that follows will be processed
//		in the usual way.
//
//		Default: on
//
// percent	If the option string contains a percent character, then the
//		percent character is interprted in one of the following ways
//		depending on what characters follow it:
//
//		A percent character followed by 1 or 2 cetal/hexadecimal
//		characters in upper or lower case is converted to the
//		character code which is represented by the cetal/hexadecimal
//		value.
//
//		A percent character followed by another percent character
//		will result in a single percent character.
//
//		A percent character followed by any other character will not
//		result in any data, but the character that follows will be
//		processed in the usual way.
//
//		Default: off
//
// carat	If the option string contains a carat character, then the
//		carat charater is interpreted as follows:
//
//		A carat character followed by a character in the ASCII range
//		of 0x21 to 0x7e converts that following character to a code
//		in the range 0x00 to 0x1f by modulo 32.	 This is the usual
//		form of representing control characters.
//
//		A carat character followed by any other character will not
//		result in any data, but the character that follows will be
//		processed in the usual way.
//
//		Default: off
//
// comments	Comments are implemented in one or more of three ways:
//
// hash		If the option string contains the hash character, then the
//		hash character is interpreted as follows:
//
//		A hash character ends all data interpretation and collection
//		until the end of the current line.  This implements commnts
//		in the shell script style.
//
//		Default: on
//
// slash	If the option string contains a slash character, then the
//		slash character is interpreted as follows:
//
//		A sequence of 2 or more slash characters ends all data
//		interpretation and collection until the end of the current
//		line.  This implements comments in the C++/C99 style.
//
//		Default: off
//
// semicolon	If the option string contains a semicolon character, then the
//		semicolon character is interpreted as follows:
//
//		A semicolon character ends all data interpretation and
//		collection until the end of the current line.  This implements
//		comments in the assembly language style.
//
//		Default: off
//
// note		At this time, C style comments like /* */ are not supported.
//		They may be added in a future version.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// function	get_item_getc_fd (internal)
//
// purpose	Input driver to get the next character from an open file
//		descriptor.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(int) != EOF : next input character
//		(int) == EOF : EOF indication
//-----------------------------------------------------------------------------
static
int
get_item_getc_fd (
    get_item_p		arg_handle
    )
{
    struct pollfd	one_poll	;
    size_t		len		;

    if ( arg_handle->buf_get_p < arg_handle->buf_put_p ) return * arg_handle->buf_get_p ++;
    if ( arg_handle->in_eof ) return EOF;
    one_poll.fd = arg_handle->in.fd.fd;
    one_poll.events = POLLIN;
    for (;;) {
	//-- Block here for input regardless if non-blocking.
	if ( poll( & one_poll, 1, -1 ) < 0 ) {
	    if ( errno == EINTR || errno == ERESTART ) continue;
	    arg_handle->in_eof = 1;
	    return EOF;
	}
	if ( one_poll.revents & ( POLLERR | POLLHUP | POLLNVAL ) ) {
	    arg_handle->in_eof = 1;
	    return EOF;
	}
	if ( ( one_poll.revents & POLLIN ) == 0 ) continue;
	len = read( arg_handle->in.fd.fd, arg_handle->buffer, arg_handle->in.fd.size );
	if ( len < 0 && errno == EAGAIN ) continue;
	if ( len <= 0 ) {
	    arg_handle->in_eof = 1;
	    return EOF;
	}
	arg_handle->buf_get_p = arg_handle->buffer + 1;
	arg_handle->buf_put_p = arg_handle->buffer + len;
	return arg_handle->buffer[0];
    }
}

//-----------------------------------------------------------------------------
// function	get_item_getc_file (internal)
//
// purpose	Input driver to get the next character from an open file.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(int) != EOF : next input character
//		(int) == EOF : EOF indication
//-----------------------------------------------------------------------------
static
int
get_item_getc_file (
    get_item_p		arg_handle
    )
{
    if ( arg_handle->buf_get_p < arg_handle->buf_put_p ) return * arg_handle->buf_get_p ++;
    else if ( arg_handle->in_eof ) return EOF;
    if ( ferror( arg_handle->in.file.file ) ) arg_handle->in_error = 1;
    return fgetc( arg_handle->in.file.file );
}

//-----------------------------------------------------------------------------
// function	get_item_getc_string (internal)
//
// purpose	Input driver to get the next character from a string.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(int) != EOF : next input character
//		(int) == EOF : EOF indication
//-----------------------------------------------------------------------------
static
int
get_item_getc_string (
    get_item_p		arg_handle
    )
{
    if ( arg_handle->buf_get_p < arg_handle->buf_put_p ) return * arg_handle->buf_get_p ++;
    if ( arg_handle->in.str.len == 0 ) return EOF;
    arg_handle->in.str.len --;
    return * (const unsigned char *)( arg_handle->in.str.str ++ );
}

//-----------------------------------------------------------------------------
// function	get_item_getc_call (internal)
//
// purpose	Input driver to get the next character from a called function.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(int) != EOF : next input character
//		(int) == EOF : EOF indication
//-----------------------------------------------------------------------------
static
int
get_item_getc_call (
    get_item_p		arg_handle
    )
{
    if ( arg_handle->buf_get_p < arg_handle->buf_put_p ) return * arg_handle->buf_get_p ++;
    if ( arg_handle->in_eof ) return EOF;
    return ( * ( arg_handle->in.call.fun ) )( arg_handle->in.call.arg );
}

//-----------------------------------------------------------------------------
// function	get_item_getc (internal)
//
// purpose	Common code to get the next character.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(int) != EOF : next input character
//		(int) == EOF : EOF indication
//-----------------------------------------------------------------------------
inline static
int
get_item_getc (
    get_item_p		arg_handle
    )
{
    int		ch	;

    if ( arg_handle->in_eof ) return EOF;
    if ( arg_handle->buf_get_p < arg_handle->buf_put_p ) return * arg_handle->buf_get_p ++;
    ch = ( * ( arg_handle->getc_p ) ) ( arg_handle );
    if ( ch == EOF ) arg_handle->in_eof = 1;
    return ch;
}

//-----------------------------------------------------------------------------
// function	get_item_ungetc (internal)
//
// purpose	Common code to do an unget of at least one character, but not
//		necessarily any more than one character.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//		2 (int) character to be pushed back onto input
//
// returns	(int)  < 0 : error
//		(int) == 0 : OK
//-----------------------------------------------------------------------------
inline static
int
get_item_ungetc (
    get_item_p		arg_handle
    ,
    int			arg_ch
    )
{
    if ( arg_handle->buf_get_p == arg_handle->buffer ) return -1;
    arg_handle->buf_get_p --;
    * arg_handle->buf_get_p = arg_ch;
    return 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_add_options
//
// purpose	Specify a set of options to enable, adding to any that are
//		already enabled.  Twice enabling an option is not an error.
//		A NULL string is like an empty string and adds nothing.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//		2 (const char *) option string of options to add
//
// returns	(int) -1 : error
//		(int)  0 : OK
//-----------------------------------------------------------------------------
int
get_item_add_options (
    get_item_p		arg_handle
    ,
    const char *	arg_options
    )
__PROTO_END__
{
    int			ch	;

    //--------------------------
    // We have to have a handle.
    //--------------------------
    if ( ! arg_handle ) return -1;

    //---------------------------------------
    // If no string is given, nothing to add.
    //---------------------------------------
    if ( ! arg_options ) return 0;

    //---------------------------------
    // Now scan string for all options.
    //---------------------------------
    while ( ( ch = * arg_options ++ ) ) {
	if	( ch == '"'  )	arg_handle->opt_quote2  = ch;
	else if	( ch == '#'  )	arg_handle->opt_hash    = ch;
	else if	( ch == '%'  )	arg_handle->opt_percent = ch;
	else if	( ch == '\'' )	arg_handle->opt_quote1  = ch;
	else if	( ch == '/'  )	arg_handle->opt_slash   = ch;
	else if	( ch == ':'  )	arg_handle->opt_colon   = ch;
	else if	( ch == ';'  )	arg_handle->opt_scolon  = ch;
	else if	( ch == '='  )	arg_handle->opt_equal   = ch;
	else if	( ch == '\\' )	arg_handle->opt_bslash  = ch;
	else if	( ch == '^'  )	arg_handle->opt_carat   = ch;
	else if	( ch == '`'  )	arg_handle->opt_bquote  = ch;
    }

    return 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_set_options
//
// purpose	Specify or change scanning options via an option character
//		string.  All previous options are replaced by the given
//		options.  A NULL string restores the defaults.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//		2 (const char *) option string
//
// returns	(int) -1 : error
//		(int)  0 : OK
//-----------------------------------------------------------------------------
int
get_item_set_options (
    get_item_p		arg_handle
    ,
    const char *	arg_options
    )
__PROTO_END__
{
    //--------------------------
    // We have to have a handle.
    //--------------------------
    if ( ! arg_handle ) return -1;

    //-----------------------------------------
    // If no string is given, use the defaults.
    //-----------------------------------------
    if ( ! arg_options ) arg_options = GET_ITEM_DEFAULT_OPTIONS;

    //------------------------
    // First reset everything.
    //------------------------
    arg_handle->opt_bslash  = -1;
    arg_handle->opt_carat   = -1;
    arg_handle->opt_colon   = -1;
    arg_handle->opt_quote2  = -1;
    arg_handle->opt_equal   = -1;
    arg_handle->opt_percent = -1;
    arg_handle->opt_quote1  = -1;
    arg_handle->opt_hash    = -1;
    arg_handle->opt_scolon  = -1;
    arg_handle->opt_slash   = -1;

    //---------------------------------
    // Now scan string for all options.
    //---------------------------------
    get_item_add_options( arg_handle, arg_options );

    return 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_del_options
//
// purpose	Specify a set of options to disable, if already enabled.
//		Disabling an option that is already disabled is not an error.
//		A NULL string is like an empty string and deletes nothing.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//		2 (const char *) option string of options to delete.
//
// returns	(int) -1 : error
//		(int)  0 : OK
//-----------------------------------------------------------------------------
int
get_item_del_options (
    get_item_p		arg_handle
    ,
    const char *	arg_options
    )
__PROTO_END__
{
    int			ch	;

    //--------------------------
    // We have to have a handle.
    //--------------------------
    if ( ! arg_handle ) return -1;

    //------------------------------------------
    // If no string is given, nothing to delete.
    //------------------------------------------
    if ( ! arg_options ) return 0;

    //---------------------------------
    // Now scan string for all options.
    //---------------------------------
    while ( ( ch = * arg_options ++ ) ) {
	if	( ch == '"'  )	arg_handle->opt_quote2  = -1;
	else if	( ch == '#'  )	arg_handle->opt_hash    = -1;
	else if	( ch == '%'  )	arg_handle->opt_percent = -1;
	else if	( ch == '\'' )	arg_handle->opt_quote1  = -1;
	else if	( ch == '/'  )	arg_handle->opt_slash   = -1;
	else if	( ch == ':'  )	arg_handle->opt_colon   = -1;
	else if	( ch == ';'  )	arg_handle->opt_scolon  = -1;
	else if	( ch == '='  )	arg_handle->opt_equal   = -1;
	else if	( ch == '\\' )	arg_handle->opt_bslash  = -1;
	else if	( ch == '^'  )	arg_handle->opt_carat   = -1;
	else if	( ch == '`'  )	arg_handle->opt_bquote  = -1;
    }

    return 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_set_hold
//
// purpose	Set the hold-at-eol mode, which causes get_item_next() to
//		always return an EOL status on the same line once an EOL
//		is reached, in effect being stuck on the same line and not
//		getting past the newline, until after get_item_flush_line()
//		is called.  This mode is useful for certain multi-context
//		parsing where a nested context needs to exit back to the
//		previous context when it reaches EOL in such a way that
//		previous context sees the same EOL.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(int) -1 : error
//		(int)  0 : OK
//-----------------------------------------------------------------------------
int
get_item_set_hold (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    if ( ! arg_handle ) return -1;
    arg_handle->opt_hold = 1;
    return ( arg_handle->in_hold = 0 );
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_unset_hold
//
// purpose	UNset the hold-at-eol mode, which causes get_item_next() to
//		no longer always return an EOL status on the same line once
//		an EOL is reached, in effect no longer being stuck on the
//		same line and now being able to get past the newline without
//		having to call get_item_flush_line().  This mode is not
//		useful most of the time other that for certain multi-context
//		parsing where a nested context needs to exit back to the
//		previous context when it reaches EOL in such a way that
//		previous context sees the same EOL.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(int) -1 : error
//		(int)  0 : OK
//-----------------------------------------------------------------------------
int
get_item_unset_hold (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    if ( ! arg_handle ) return -1;
    arg_handle->opt_hold = 0;
    return ( arg_handle->in_hold = 0 );
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_new_fd
//
// purpose	Create a new handle to an item reading instance to keep the
//		reading state, and associate it with an open file descriptor.
//
// arguments	1 (int) open file descriptor to read from
//
// returns	(get_item_p) pointer handle to new item reading instance.
//-----------------------------------------------------------------------------
get_item_p
get_item_new_fd (
    int			arg_fd
    )
__PROTO_END__
{
    get_item_p		new_handle	;
    size_t		buf_size	;

    if ( arg_fd < 0 ) return NULL;

#ifdef _SC_PAGESIZE
    buf_size = sysconf( _SC_PAGESIZE );
#else
    buf_size = getpagesize();
#endif
    if ( ! ( new_handle = malloc( buf_size + (size_t) ( ( (get_item_t *) 0 )->buffer ) ) ) ) return NULL;

    new_handle->getc_p = get_item_getc_fd;
    new_handle->in.fd.size = buf_size;
    new_handle->in.fd.fd = arg_fd;
    get_item_set_options( new_handle, NULL );

    new_handle->buf_end_p = new_handle->buffer + buf_size;
    new_handle->buf_put_p = new_handle->buf_end_p;
    new_handle->buf_get_p = new_handle->buf_end_p;

    new_handle->opt_tab  = 8;

    new_handle->opt_hold = 0;
    new_handle->in_hold  = 0;
    new_handle->in_eof   = 0;
    new_handle->in_error = 0;
    new_handle->pos_cnt  = 0;
    new_handle->pos_col  = 0;
    new_handle->pos_row  = 0;
    new_handle->beg_cnt  = 0;
    new_handle->beg_col  = 0;
    new_handle->beg_row  = 0;
    new_handle->end_cnt  = 0;
    new_handle->end_col  = 0;
    new_handle->end_row  = 0;

    return new_handle;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_new_file
//
// purpose	Create a new handle to an item reading instance to keep the
//		reading state, and associate it with an open standard file.
//
// arguments	1 (FILE *) open file to read from
//
// returns	(get_item_p) pointer handle to new item reading instance.
//-----------------------------------------------------------------------------
get_item_p
get_item_new_file (
    FILE *		arg_file
    )
__PROTO_END__
{
    get_item_p		new_handle	;

    if ( ! arg_file ) return NULL;
    if ( ! ( new_handle = malloc( sizeof (get_item_t) ) ) ) return NULL;
    new_handle->getc_p = get_item_getc_file;
    new_handle->in.file.file = arg_file;
    get_item_set_options( new_handle, NULL );

    new_handle->buf_end_p = new_handle->buffer + sizeof (new_handle->buffer);
    new_handle->buf_put_p = new_handle->buf_end_p;
    new_handle->buf_get_p = new_handle->buf_end_p;

    new_handle->opt_tab  = 8;

    new_handle->in_eof   = 0;
    new_handle->in_error = 0;
    new_handle->pos_cnt  = 0;
    new_handle->pos_col  = 0;
    new_handle->pos_row  = 0;
    new_handle->beg_cnt  = 0;
    new_handle->beg_col  = 0;
    new_handle->beg_row  = 0;
    new_handle->end_cnt  = 0;
    new_handle->end_col  = 0;
    new_handle->end_row  = 0;

    return new_handle;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_new_string
//
// purpose	Create a new handle to an item reading instance to keep the
//		reading state, and associate it with a character string that
//		contains the data to be treated as input.
//
// arguments	1 (const char *) string to read from
//		2 (size_t) length of string to read from (or ~0 with null)
//
// returns	(get_item_p) pointer handle to new item reading instance.
//-----------------------------------------------------------------------------
get_item_p
get_item_new_string (
    const char *	arg_str
    ,
    size_t		arg_len
    )
__PROTO_END__
{
    get_item_p		new_handle	;

    if ( ! arg_str ) return NULL;
    if ( ! ( new_handle = malloc( sizeof (get_item_t) ) ) ) return NULL;

    new_handle->getc_p = get_item_getc_string;
    new_handle->in.str.str = arg_str;
    new_handle->in.str.len = arg_len == ~ (size_t) 0 ? strlen( arg_str ) : arg_len;
    get_item_set_options( new_handle, NULL );

    new_handle->buf_end_p = new_handle->buffer + sizeof (new_handle->buffer);
    new_handle->buf_put_p = new_handle->buf_end_p;
    new_handle->buf_get_p = new_handle->buf_end_p;

    new_handle->opt_tab  = 8;

    new_handle->in_eof   = 0;
    new_handle->in_error = 0;
    new_handle->pos_cnt  = 0;
    new_handle->pos_col  = 0;
    new_handle->pos_row  = 0;
    new_handle->beg_cnt  = 0;
    new_handle->beg_col  = 0;
    new_handle->beg_row  = 0;
    new_handle->end_cnt  = 0;
    new_handle->end_col  = 0;
    new_handle->end_row  = 0;

    return new_handle;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_new_call
//
// purpose	Create a new handle to an item reading instance to keep the
//		reading state, and associate it with a function callback.
//
// arguments	1 (int (*)(void *)) pointer to callback function
//		2 (void *) arbitrary pointer to pass to callback function
//
// returns	(get_item_p) pointer handle to new item reading instance.
//-----------------------------------------------------------------------------
get_item_p
get_item_new_call (
    int ( *		arg_fun		)(void *)
    ,
    void *		arg_arg
    )
__PROTO_END__
{
    get_item_p		new_handle	;

    if ( ! arg_fun ) return NULL;
    if ( ! ( new_handle = malloc( sizeof (get_item_t) ) ) ) return NULL;

    new_handle->getc_p = get_item_getc_call;
    new_handle->in.call.fun = arg_fun;
    new_handle->in.call.arg = arg_arg;
    get_item_set_options( new_handle, NULL );

    new_handle->buf_end_p = new_handle->buffer + sizeof (new_handle->buffer);
    new_handle->buf_put_p = new_handle->buf_end_p;
    new_handle->buf_get_p = new_handle->buf_end_p;

    new_handle->opt_tab  = 8;

    new_handle->in_eof   = 0;
    new_handle->in_error = 0;
    new_handle->pos_cnt  = 0;
    new_handle->pos_col  = 0;
    new_handle->pos_row  = 0;
    new_handle->beg_cnt  = 0;
    new_handle->beg_col  = 0;
    new_handle->beg_row  = 0;
    new_handle->end_cnt  = 0;
    new_handle->end_col  = 0;
    new_handle->end_row  = 0;

    return new_handle;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_destroy
//
// purpose	Destroy a get_item instance that is no longer needed.
//		The handle is no longer valid after this function is called.
//
// arguments	1 (get_item_p) pointer handle to instance to destroy
//
// returns	(int) -1 : error
//		(int)  0 : OK
//-----------------------------------------------------------------------------
int
get_item_destroy (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    if ( ! arg_handle ) return -1;
    arg_handle->getc_p = NULL;
    free( arg_handle );
    return 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_begin_cnt
//
// purpose	Get the beginning position of the most recently retrieved item
//		as the number of characters from the beginning of the input
//		file as read by get_item_next().  The first character will be
//		returned as the value 0.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(long long) == -1 : error
//		(long long) >=  0 : item position in input
//-----------------------------------------------------------------------------
long long
get_item_begin_cnt (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    return arg_handle ? arg_handle->beg_cnt : -1LL;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_begin_col
//
// purpose	Get the beginning position of the most recently retrieved item
//		as the number of characters from the beginning of the line the
//		item began on.  The first column will be returned as the value
//		0.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(long) == -1 : error
//		(long) >=  0 : item position in input
//-----------------------------------------------------------------------------
long
get_item_begin_col (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    return arg_handle ? arg_handle->beg_col : -1L;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_begin_row
//
// purpose	Get the beginning position of the most recently retrieved item
//		as the number of the row the item began on.  The first row will
//		be returned as the value 0.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(long) == -1 : error
//		(long) >=  0 : item position in input
//-----------------------------------------------------------------------------
long
get_item_begin_row (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    return arg_handle ? arg_handle->beg_row : -1L;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_end_cnt
//
// purpose	Get the ending position of the most recently retrieved item
//		as the number of characters from the beginning of the input
//		file as read by get_item_next().  The first character will be
//		returned as the value 0.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(long long) == -1 : error
//		(long long) >=  0 : item position in input
//-----------------------------------------------------------------------------
long long
get_item_end_cnt (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    return arg_handle ? arg_handle->end_cnt : -1LL;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_end_col
//
// purpose	Get the ending position of the most recently retrieved item
//		as the number of characters from the beginning of the line the
//		item began on.  The first column will be returned as the value
//		0.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(long) == -1 : error
//		(long) >=  0 : item position in input
//-----------------------------------------------------------------------------
long
get_item_end_col (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    return arg_handle ? arg_handle->end_col : -1L;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_end_row
//
// purpose	Get the ending position of the most recently retrieved item
//		as the number of the row the item began on.  The first row will
//		be returned as the value 0.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(long) == -1 : error
//		(long) >=  0 : item position in input
//-----------------------------------------------------------------------------
long
get_item_end_row (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    return arg_handle ? arg_handle->end_row : -1L;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_actual_len
//
// purpose	Get the actual data length of the most recently retrieved item.
//		The actual data length is the length of the data item after
//		conversion regardless of the size of the space it was being
//		stored in.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(size_t) == ~0 : length unavailable, bad handle
//		(size_t) != ~0 : actual data item length
//-----------------------------------------------------------------------------
size_t
get_item_actual_len (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    return arg_handle ? arg_handle->actual_len : ~ (size_t) 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_stored_len
//
// purpose	Get the stored data length of the most recently retrieved item.
//		The stored data length is the length of the data item after
//		conversion, as truncated to fit the available space it was
//		being stored in.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(size_t) == ~0 : length unavailable, bad handle
//		(size_t) != ~0 : stored data item length
//-----------------------------------------------------------------------------
size_t
get_item_stored_len (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    return arg_handle ? arg_handle->stored_len : ~ (size_t) 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_next
//
// purpose	Get the next input space delimited item, storing the item
//		string into the given target.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//		2 (char *) pointer to target string to copy item string to
//		3 (size_t) maximum available target space
//
// returns	(int) == -4 : error, invalid arguments
//		(int) == -3 : item not read due to file I/O error
//		(int) == -2 : item not read due to end of file
//		(int) == -1 : item not read due to end of line
//		(int) ==  0 : item read and stored complete
//		(int) ==  1 : item read and stored truncated
//-----------------------------------------------------------------------------
// note		If the item space target is not large enough, then truncation
//		will occur and the return code will be 1 (instead of 0).
//		The complete item will be read from the file as if the space
//		were large enough, so the next call to get_item_next() will
//		correctly return the next item.  The truncated item will be
//		properly terminated at the end of the available space.
//-----------------------------------------------------------------------------
int
get_item_next (
    get_item_p		arg_handle
    ,
    char *		arg_item_ptr
    ,
    size_t		arg_item_len
    )
__PROTO_END__
{

//-----------------------------------------------------------------------------
// The following tables assume the ASCII character set.
// This implementation does not support other character sets.
//-----------------------------------------------------------------------------
    static const signed char	newline_table	[256]	= {
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0, 0x1,	0x2, 0x4, 0x8,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0,
	   0,	0,   0,	  0,   0,   0,	 0,   0,   0,	0,   0,	  0,   0,   0,	 0,   0
    };
    static const signed char	cetal_table	[256]	= {
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1
    };
    static const signed char	escape_table	[256]	= {
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,'\a','\b',	 -1,  -1,'\e','\f',  -1,  -1,  -1,  -1,	 -1,  -1,  -1,'\n',  -1,
	  -1,  -1,'\r',	 -1,'\t',  -1,'\v',  -1,  -1,  -1,'\0',	 -1,  -1,  -1,	-1,  -1,
	  -1,'\a','\b',	 -1,  -1,'\e','\f',  -1,  -1,  -1,  -1,	 -1,  -1,  -1,'\n',  -1,
	  -1,  -1,'\r',	 -1,'\t',  -1,'\v',  -1,  -1,  -1,'\0',	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1
    };

    unsigned long long	pos_cnt			;
    unsigned long	pos_col			;
    unsigned long	pos_row			;

    char *		item_ptr		;
    char *		item_end		;

    size_t		actual_len		;

    int			ch			;
    int			ac			;

    int			in_bslash		;
    int			in_carat		;
    int			in_comment		;
    int			in_newline		;
    int			in_percent		;
    int			in_quote		;
    int			in_slash		;


    //-----------------
    // Check arguments.
    //-----------------
    if ( ! arg_handle ) return -4;
    if ( ! arg_item_ptr ) return -4;
    if ( arg_item_len < 2 ) return -4;

    //----------------------------------------------------
    // If in a holding pattern on an EOL, then return now.
    //----------------------------------------------------
    if ( arg_handle->in_hold ) return -1;

    //------------------------------------
    // Predetermine end of target pointer.
    //------------------------------------
    item_end = ( item_ptr = arg_item_ptr ) + arg_item_len;

    //-----------------------------------
    // Get the current position counters.
    //-----------------------------------
    pos_cnt = arg_handle->pos_cnt;
    pos_col = arg_handle->pos_col;
    pos_row = arg_handle->pos_row;

    //------------------------------------
    // Initially start with one character.
    //------------------------------------
    ch = get_item_getc( arg_handle );

    //-------------------------------------------
    // Restore or reset the space scanning modes.
    //-------------------------------------------
    in_newline = arg_handle->in_newline;
    in_comment = arg_handle->in_comment;
    in_bslash = 0;
    in_slash = 0;

    //-------------------------------------------------------------------
    // Skip previous newlines, spaces, and comments, until data is found.
    //-------------------------------------------------------------------
    for (;;) {

	//-------------------------------
	// If this is EOF, return it now.
	//-------------------------------
	if ( ch == EOF ) {
	    if ( in_newline ) {
		++ pos_row; pos_col = 0;
	    }
	    arg_handle->pos_cnt = arg_handle->beg_cnt = arg_handle->end_cnt = pos_cnt;
	    arg_handle->pos_col = arg_handle->beg_col = arg_handle->end_col = pos_col;
	    arg_handle->pos_row = arg_handle->beg_row = arg_handle->end_row = pos_row;
	    arg_handle->in_eof = 1;
	    return -2;
	}

	//-----------------------------------
	// If still in a newline sequence ...
	//-----------------------------------
	if ( in_newline ) {

	    //----------------------------------------------------------------------
	    // If this is a non-newline character, or a newline character that has
	    // already been in this newline sequence, then end the current sequence,
	    // count the new row, reset the column number, reset newline and comment
	    // modes, and reprocess this character from the top of the loop.
	    //----------------------------------------------------------------------
	    if ( newline_table[ch] == 0 || newline_table[ch] & in_newline ) {
		++ pos_row; pos_col = 0;
		in_comment = in_bslash = in_newline = 0;
		continue;
	    }

	    //------------------------------------------------------------
	    // Otherwise it is a newline which has not been seen before in
	    // this newline sequence, so record that it has been seen now.
	    //------------------------------------------------------------
	    in_newline |= newline_table[ch];
	}

	//----------------------------------------
	// Else if still in an escape sequence ...
	//----------------------------------------
	else if ( in_bslash ) {

	    //-----------------------------------------------------
	    // If this is a newline (escaped), then return nothing.
	    // Just start a new newline sequence to flush it.
	    //-----------------------------------------------------
	    if ( newline_table[ch] ) {
		in_newline = newline_table[ch];
	    }

	    //------------------------------------------------
	    // Else if outside of a comment an escape sequence
	    // represents some data, so break out of the space
	    // scanning loop to go to the data scanning loop.
	    //------------------------------------------------
	    else if ( ! in_comment ) {
		-- pos_cnt;
		break;
	    }

	    //-----------------------------------------------------------
	    // Otherwise inside a comment, an escape sequence is ignored.
	    //-----------------------------------------------------------
	    in_bslash = 0;
	}

	//------------------------------------------------------------------
	// Else if half way into a possible double-slash comment starter ...
	//------------------------------------------------------------------
	else if ( in_slash ) {

	    //------------------------------------------------------------
	    // If the sequence is not a double slash, then this represents
	    // data, so break out of the space scanning loop to go to the
	    // data scanning loop which will resume the slash mode.
	    //------------------------------------------------------------
	    if ( ch != '/' ) {
		-- pos_cnt;
		break;
	    }

	    //----------------------------------------------------
	    // Otherwise this begins a double-slash style comment.
	    //----------------------------------------------------
	    pos_col += 2;
	    pos_cnt += 2;
	    in_slash = 0;
	    in_comment = 1;
	}

	//----------------------------------------------------------
	// If this is a new newline character, return a new EOL now.
	//----------------------------------------------------------
	else if ( newline_table[ch] ) {
	    arg_handle->beg_cnt = arg_handle->end_cnt = pos_cnt;
	    arg_handle->beg_col = arg_handle->end_col = pos_col;
	    arg_handle->beg_row = arg_handle->end_row = pos_row;
	    ++ pos_cnt;
	    ++ pos_col;
	    arg_handle->pos_cnt = pos_cnt;
	    arg_handle->pos_col = pos_col;
	    arg_handle->pos_row = pos_row;
	    arg_handle->in_newline = newline_table[ch];
	    arg_handle->in_comment = 0;
	    if ( arg_handle->opt_hold ) arg_handle->in_hold = 1;
	    return -1;
	}

	//----------------------------------------------------------
	// If this is an escape character, start an escape sequence.
	//----------------------------------------------------------
	else if ( ch == arg_handle->opt_bslash ) {
	    in_bslash = 3;
	}

	//-------------------------------------------------------------
	// If this is a tab or space, or is in a comment, then skip it.
	//-------------------------------------------------------------
	else if ( ch == '\t' ) {
	    if ( arg_handle->opt_tab > 0 ) {
		pos_col += arg_handle->opt_tab - pos_col % arg_handle->opt_tab;
	    } else {
		++ pos_col;
	    }
	}
	else if ( ch == ' ' || in_comment ) {
	    ++ pos_col;
	}

	//----------------------------------------------------------
	// If this is a comment start character, enter comment mode.
	//----------------------------------------------------------
	else if ( ch == arg_handle->opt_hash || ch == arg_handle->opt_scolon ) {
	    ++ pos_col;
	    in_comment = 1;
	}
	//------------------------------------------------------------------
	// If this is a possible double-slash comment, enter a special mode.
	// Do not update counters yet, waiting until the next character.
	//------------------------------------------------------------------
	else if ( ch == arg_handle->opt_slash ) {
	    in_slash = 1;
	}

	//------------------------------------------------------
	// If not a raw control character, then it must be data.
	//------------------------------------------------------
	else if ( ch > 32 /* ASCII */ ) {
	    break;
	}

	//--------------------------------------
	// Get another character for next round.
	//--------------------------------------
	++ pos_cnt;
	ch = get_item_getc( arg_handle );
    }

    //--------------------------
    // Record where data begins.
    //--------------------------
    arg_handle->beg_cnt = pos_cnt;
    arg_handle->beg_col = pos_col;
    arg_handle->beg_row = pos_row;

    //----------------------------------------------------
    // Reset all the data scanning modes and data lengths.
    //----------------------------------------------------
    in_comment = 0;
    in_percent = 0;
    in_carat = 0;
    in_quote = 0;
    actual_len = 0;

    //---------------------------------------------
    // This does not really need to be initialized,
    // but the compiler cannot figure it out.
    //---------------------------------------------
    ac = 0;

    //-----------------------------------------------
    // Scan and collect data until something ends it.
    //-----------------------------------------------
    for (;;) {

	//--------------------------------------------------------------------
	// If possibly half-way in double-slash comment, check for next slash.
	//--------------------------------------------------------------------
	if ( in_slash ) {
	    ++ pos_cnt;
	    ++ pos_col;

	    //---------------------------------------------
	    // If this is a double-slash, end the data now.
	    //---------------------------------------------
	    if ( ch == '/' ) {
		arg_handle->in_comment = 1;
		++ pos_cnt;
		++ pos_col;
		break;
	    }

	    //-------------------------------------------------------------------
	    // Otherwise, store the slash as data and process the next character.
	    //-------------------------------------------------------------------
	    ++ actual_len;
	    if ( item_ptr < item_end ) * item_ptr ++ = '/';
	    in_slash = 0;
	}

	//---------------------------------------
	// If in percent code processing mode ...
	//---------------------------------------
	if ( in_percent ) {

	    //--------------------------------------------------------------------
	    // If this is not a cetal (hexadecimal) character, store what has been
	    // accumulated so far, then cancel the percent mode and reinterpret.
	    //--------------------------------------------------------------------
	    if ( cetal_table[ch] == -1 ) {
		if ( in_percent < 2 ) {
		    ++ actual_len;
		    if ( item_ptr < item_end ) * item_ptr ++ = ac;
		}
		in_percent = 0;
		continue;
	    }

	    //------------------------------------------
	    // Otherwise accumulate another cetal digit.
	    //------------------------------------------
	    ac <<= 4;
	    ac += cetal_table[ch];
	    if ( -- in_percent == 0 ) {
		++ actual_len;
		if ( item_ptr < item_end ) * item_ptr ++ = ac;
	    }
	    arg_handle->end_cnt = pos_cnt;
	    arg_handle->end_col = pos_col;
	    arg_handle->end_row = pos_row;
	    ++ pos_col;
	}

	//----------------------------------------------
	// Else if in backslash code processing mode ...
	//----------------------------------------------
	else if ( in_bslash ) {

	    //--------------------------------------------
	    // If this is a newline, the item ends anyway.
	    //--------------------------------------------
	    if ( newline_table[ch] ) {
		++ pos_cnt;
		break;
	    }

	    //-------------------------------------------
	    // If followed by one of the escape code
	    // characters then use its decoded value now.
	    //-------------------------------------------
	    if ( escape_table[ch] >= 0 ) {
		++ actual_len;
		if ( item_ptr < item_end ) * item_ptr ++ = escape_table[ch];
	    }

	    //-----------------------------------------------------------------
	    // If followed by 'x' or 'X' then switch to cetal/hexadecimal mode.
	    //-----------------------------------------------------------------
	    else if ( ch == 'x' || ch == 'X' ) {
		in_bslash = 0;
		in_percent = 2;
	    }

	    //----------------------------------------------
	    // Else if not a valid octal digit character ...
	    //----------------------------------------------
	    else if ( ch < '0' || '7' < ch ) {

		//------------------------------------------------------
		// If any octal digits have been accumulated, then store
		// the accumulated code and reinterpret this character.
		//------------------------------------------------------
		if ( in_bslash < 3 ) {
		    ++ actual_len;
		    if ( item_ptr < item_end ) * item_ptr ++ = ac;
		    in_bslash = 0;
		    continue;
		}

		//-----------------------------------------------------
		// Otherwise this is the first character following the
		// backslash.  Store it as data without intrepretation.
		//-----------------------------------------------------
		++ actual_len;
		if ( item_ptr < item_end ) * item_ptr ++ = ch;
		in_bslash = 0;
	    }

	    //----------------------------------------------------
	    // Else this is a valid octal digit, so accumulate it.
	    //----------------------------------------------------
	    else {
		ac <<= 3;
		ac += ( ch - '0' );
		if ( -- in_bslash == 0 ) {
		    ++ actual_len;
		    if ( item_ptr < item_end ) * item_ptr ++ = ac;
		}
	    }

	    arg_handle->end_cnt = pos_cnt;
	    arg_handle->end_col = pos_col;
	    arg_handle->end_row = pos_row;
	    ++ pos_col;
	}

	//---------------------------------------------------------------------
	// Else if in carat processing mode, interpret as a shift control code.
	//---------------------------------------------------------------------
	else if ( in_carat ) {
	    in_carat = 0;
	    if ( ch < 33 || 126 < ch ) {
		continue;
	    }
	    ++ actual_len;
	    if ( item_ptr < item_end ) * item_ptr ++ = ch & 0x1f;
	    arg_handle->end_cnt = pos_cnt;
	    arg_handle->end_col = pos_col;
	    arg_handle->end_row = pos_row;
	    ++ pos_col;
	}

	//----------------------------------------------------------------
	// Else if this is an end of file, remember it and end the data.
	// The EOF status will be returned on the next call, not this one.
	// Count it now, not later, so it is counted only once.
	//----------------------------------------------------------------
	else if ( ch == EOF ) {
	    arg_handle->in_eof = 1;
	    break;
	}

	//-------------------------------------------------------------------------
	// Else if this is one of these special coding characters, start that mode.
	//-------------------------------------------------------------------------
	else if ( ch == arg_handle->opt_percent ) {
	    in_percent = 2;
	    ac = 0;
	    ++ pos_col;
	}
	else if ( ch == arg_handle->opt_bslash ) {
	    in_bslash = 3;
	    ac = 0;
	    ++ pos_col;
	}
	else if ( ch == arg_handle->opt_carat ) {
	    in_carat = 1;
	    ++ pos_col;
	}

	//----------------------------------------------------------------------
	// Else if this is a newline, the item ends, even if in a quoted string.
	// Do not count the line.  That is done in the next call.
	//----------------------------------------------------------------------
	else if ( newline_table[ch] ) {
	    get_item_ungetc( arg_handle, ch );
	    break;
	}

	//-----------------------------------------
	// Else if currently in a quoted string ...
	//-----------------------------------------
	else if ( in_quote ) {

	    //-------------------------------------------------------------------
	    // If this is the same quote as started this string, it ends it here.
	    //-------------------------------------------------------------------
	    if ( ch == in_quote ) {
		in_quote = 0;
	    }

	    //--------------------------------------------------------------
	    // Else it is a non-quote or a different quote and is just data.
	    //--------------------------------------------------------------
	    else {
		++ actual_len;
		if ( item_ptr < item_end ) * item_ptr ++ = ch;
	    }

	    //-------------------------------------------
	    // Update end locators even for ending quote.
	    //-------------------------------------------
	    arg_handle->end_cnt = pos_cnt;
	    arg_handle->end_col = pos_col;
	    arg_handle->end_row = pos_row;
	    ++ pos_col;
	}

	//-----------------------------------------------------------------
	// Else if this is any one of up to three possible quote characters
	// then start the quote mode while remembering which quote it is.
	//-----------------------------------------------------------------
	else if ( ch == arg_handle->opt_quote1 ||
		  ch == arg_handle->opt_quote2 ||
		  ch == arg_handle->opt_bquote ) {
	    ++ pos_col;
	    in_quote = ch;
	}

	//--------------------------------------------
	// Else if this is an assignment character ...
	//--------------------------------------------
	else if ( ch == arg_handle->opt_equal || ch == arg_handle->opt_colon ) {

	    //----------------------------------------------
	    // If some data is already collected, end it and
	    // do the assignment character in the next call.
	    //----------------------------------------------
	    if ( actual_len ) {
		get_item_ungetc( arg_handle, ch );
		break;
	    }

	    //-------------------------------------------------
	    // Otherwise, this character is data all by itself.
	    //-------------------------------------------------
	    actual_len = 1;
	    * item_ptr ++ = ch;
	    break;
	}

	//----------------------------------------------------
	// Else if this is a space or tab, the data item ends.
	//----------------------------------------------------
	else if ( ch == ' ' || ch == '\t' ) {
	    if ( ch == '\t' && arg_handle->opt_tab > 0 ) {
		pos_col += arg_handle->opt_tab - pos_col % arg_handle->opt_tab;
	    } else {
		++ pos_col;
	    }
	    ++ pos_cnt;
	    break;
	}

	//---------------------------------------------------------------
	// Else if this is a comment start character, the data item ends.
	//---------------------------------------------------------------
	else if ( ch == arg_handle->opt_hash ||
		  ch == arg_handle->opt_scolon ) {
	    arg_handle->in_comment = ch;
	    ++ pos_cnt;
	    break;
	}

	//---------------------------------------------------------------------
	// Else if this might be a comment start sequence, set a mode to check.
	// Do not update counters yet, waiting until the next character.
	//---------------------------------------------------------------------
	else if ( ch == arg_handle->opt_slash ) {
	    in_slash = 1;
	    -- pos_cnt;
	}

	//-------------------------------
	// Else store the data character.
	//-------------------------------
	else {
	    ++ actual_len;
	    if ( item_ptr < item_end ) * item_ptr ++ = ch;
	    arg_handle->end_cnt = pos_cnt;
	    arg_handle->end_col = pos_col;
	    arg_handle->end_row = pos_row;
	    ++ pos_col;
	}

	//-----------------------------------------------------------
	// Get another character from the input source for more data.
	//-----------------------------------------------------------
	++ pos_cnt;
	ch = get_item_getc( arg_handle );
    }

    //-------------
    // Return data.
    //-------------
    arg_handle->in_newline = 0;
    ac = 0;
    if ( item_ptr == item_end ) {
	-- item_ptr;
	ac = 1;
    }
    * item_ptr = 0;

    arg_handle->actual_len = actual_len;
    arg_handle->stored_len = item_ptr - arg_item_ptr;
    arg_handle->pos_cnt = pos_cnt;
    arg_handle->pos_col = pos_col;
    arg_handle->pos_row = pos_row;

    return ac;

}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	get_item_flush_line
//
// purpose	Flush the current line.
//
// arguments	1 (get_item_p) handle pointer to get_item instance
//
// returns	(int) == -4 : error, invalid arguments
//		(int) == -3 : item not read due to file I/O error
//		(int) == -2 : item not read due to end of file
//		(int) ==  0 : line flushed OK
//-----------------------------------------------------------------------------
int
get_item_flush_line (
    get_item_p		arg_handle
    )
__PROTO_END__
{
    char	item_space	[8];
    int		rc		;

    for (;;) {
	if ( ( rc = get_item_next( arg_handle, item_space, sizeof (item_space) ) ) == -1 ) {
	    arg_handle->in_hold = 0;
	    return 0;
	}
	if ( rc < 0 ) return rc;
    }
}

