//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// 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 "string_lib.h"

//-----------------------------------------------------------------------------
// functions	str_select_*[_va]
//
// purpose	Select a substring from a given string based on a command set
//		specified in a control string which can use arguments of the
//		function call, and return the substring by various methods.
//
// control string command set characters:
//
//	^	Set the cursor to the beginning of the given string.
//	
//	$	Set the cursor to the end of the given string.
//	
//	[	Set the substring begin pointer to the current cursor and
//		include the delimiter it points to.
//	
//	(	Set the substring begin pointer to the current cursor and
//		exclude the delimiter it points to.
//	
//	{	Set the substring begin pointer to the current cursor and
//		exclude the delimiter it points to as well as any leading
//		whitespace characters.
//
//	}	Set the substring end pointer to the current cursor and
//		exclude the delimiter it points to as well as any trailing
//		whitespace characters.
//
//	)	Set the substring end pointer to the current cursor and
//		exclude the delimiter it points to.
//	
//	]	Set the substring end pointer to the current cursor and
//		include the delimiter it points to.
//	
//	w W	Specify the next search is for a group of whitespace
//		characters, where one or more of any combination together
//		is considered to be a single delimiter.
//	
//	n N	Specify the next search is for a newline, which may also
//		include a carriage return if there is one before the newline.
//		Characters formfeed and vertical tab also count as newline
//		characters.
//
//	,	Specify the next search is for a ',' character without using
//		an argument to pass the character code.
//
//	.	Specify the next search is for a '.' character without using
//		an argument to pass the character code.
//	
//	/	Specify the next search is for a '/' character without using
//		an argument to pass the character code.
//
//	:	Specify the next search is for a ':' character without using
//		an argument to pass the character code.
//
//	;	Specify the next search is for a ';' character without using
//		an argument to pass the character code.
//
//	@	Specify the next search is for a '@' character without using
//		an argument to pass the character code.
//
//	|	Specify the next search is for a '|' character without using
//		an argument to pass the character code.
//	
//	=	Specify the next search is for the character that immediately
//		follows this = in the control string.
//
//	c C	Specify the next search is for a single character given in an
//		argument as (int).
//
//	l L	Specify the next search is for a single character in a list of
//		characters given in a string argument as (const char *).
//	
//	t T	Specify the next search is for a single character that when
//		used to index a character table/array yields a non-zero value.
//		The case insensitive flag does not apply to a search by
//		character table.
//	
//	x X	When used with L or T, specifies that the test for the
//		delimiter character is inverted, that is, for L, the first
//		character NOT in the list, and for T, the delimiter is the
//		first character NOT in the table (e.g. has a table value of 0).
//	
//	s S	Specify the next search is for a string given in an argument as
//		(const char *).
//	
//	m M	Specify the next search is for a string given in non-terminated
//		form in two arguments as (const char *) and (size_t).
//	
//	i I	When used with S or M, specifies that the test for the
//		delimiter character string is done case-insensitive.
//
//	&	Specify that if a search is incomplete (fewer delimiters are
//		found than requested) then the current delimiter state is not
//		changed.
//
//	*	Specify that if a search is incomplete (fewer delimiters are
//		found than requested) then the current delimiter state is set
//		to the last delimiter found.
//
//		If neither & nor * is specified, and a search is incomplete,
//		then the current delimiter is set to the end of the subject
//		string in the specified direction.
//
//	?	Specify that if the next delimiter follows immediately with
//		no characters between the current delimiter and the next,
//		then skip to the next delimiter without counting it, otherwise
//		it would be counted.  This will be done even if the count is
//		zero.  This is useful for pre-skipping leading whitespace.
//
//	#	Specify that the next search is to be repeated a number of
//		times where the number is given in an argument as (int).
//	
//	digits	Specify that the next search is to be repeated a number of
//		times where the number is the value represented by the decimal
//		digits.
//	
//	>	Specify to do a forward search now.
//	
//	<	Specify to do a reverse search now.
//
// Various functions differ in how they return the selected substring and
// receive the variable arguments:
//
// str_select_dup()
//	allocates space for the selected substring, copies the substring to
//	that space, and returns a pointer to it.  The caller is responsible for
//	freeing the space when it no longer needs it.  The original string is
//	not modified.
//
// str_select_dup_va()
//	works like str_select_dup() except that it receives the arguments in
//	va_list.
//
// str_select_cut()
//	terminates the selected substring in place in the given string, and
//	returns a pointer to it.  The original string is therefore modified by
//	that termination.  If the original string was allocated, the caller
//	must not free it until the substring is no longer needed.
//
// str_select_cut_va()
//	works like str_select_cut() except that it receives the arguments in
//	va_list.
//
// str_select_cpy()
//	copies the selected substring, truncated to fit in the space given by
//	the caller, with termination always included.  The original string is
//	not modified.
//
// str_select_cpy_va()
//	works like str_select_cpy() except that it receives the arguments in
//	va_list.
//
// str_select_app()
//	appends the selected substring, truncated to fit at the end of the
//	existing terminated string in the space given by the caller, with
//	termination always included.  The original string is not modified.
//
// str_select_app_va()
//	works like str_select_app() except that it receives the arguments in
//	va_list.
//
// str_select_ptr()
//	stores the pointer and end pointer to the unterminated selected
//	substring within the original string, into the locations specified by
//	the caller, and returns the length of the substring.  The original
//	string is not modified.  If the original string was allocated, the
//	caller must not free it until the substring is no longer needed.
//
// str_select_ptr_va()
//	works like str_select_ptr() except that it receives the arguments in
//	va_list.
//
// examples:
//
// To remove the directory path and extension from a file name:
//	str_select_dup( fullname, "$1c<($1c<)", '/', '.' ) ... or simply:
//	str_select_dup( fullname, "$/<($1.<)" )
//
// To extract the directory from a path (aka dirname):
//	str_select_dup( fullpath, "$1c<)", '/' ) ... or simply:
//	str_select_dup( fullpath, "$/<)" )
//
// To extract the base name from a path (aka basename):
//	str_select_dup( fullpath, "$1c<(", '/' ) ... or simply:
//	str_select_dup( fullpath, "$/<(" )
//
// To extract the username part of an email address:
//	str_select_dup( email, "^1c>)", '@' ) ... or simply:
//	str_select_dup( email, "^@>)" )
//
// To extract the hostname part of an email address:
//	str_select_dup( email, "^1c>(", '@' ) ... or simply:
//	str_select_dup( email, "^@>(" )
//
// To extract the second level domain from a fully qualified domain name:
//	str_select_dup( fqdn, "$2c<(", '.' ) ... or simply:
//	str_select_dup( fqdn, "$2.<(" )
//
// To extract an email address from within <>:
//	str_select_dup( email, "$c!>(^c!<)", '<', '>' );
//-----------------------------------------------------------------------------

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//-----------------------------------------------------------------------------
// Define a table to look up line separator character codes.
//-----------------------------------------------------------------------------
static unsigned char	newline_table	[256]	= {
//-----------------------------------------------------------------------------
//              vertical tab       form feed
//                  newline \     / carriage return
//------------------------ \ \   / / ------------------------------------------
	0,0,0,0,0,0,0,0,0,0,2,2,2,1,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 };


__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	str_select_ptr_va
//
// arguments	1 (char * *) where to store pointer to selected substring
//		2 (char * *) where to store end pointer to
//		3 (const char *) string to select a substring from
//		4 (const char *) selection command string
//		5 (va_list) already started arguments used by command string
//
// returns	(size_t) selected substring length
//		(size_t) ~0 if error
//-----------------------------------------------------------------------------
size_t
str_select_ptr_va (
    char * *		arg_ss_ptr_p
    ,
    char * *		arg_ss_end_p
    ,
    const char *	arg_string
    ,
    const char *	arg_control
    ,
    va_list		arg_va_list
    )
__PROTO_END__
{
    const char *	cursor_ptr	;
    const char *	cursor_end	;
    const char *	string_ptr	;
    const char *	string_end	;
    const char *	substr_ptr	;
    const char *	substr_end	;
    const char *	search_ptr	;

    const char *	find_ptr	;
    const char *	find_end	;
    const char *	scan_ptr	;

    size_t			search_len	;

    int				control_ch	;
    int				search_ch	;

    int				nocase		;
    int				digits		;
    int				direct		;
    int				incomp		;
    int				invert		;
    int				repeat		;
    int				search		;
    int				simmed		;
    int				spaces		;


    search_ptr	= NULL;
    scan_ptr	= NULL;
    search_len	= 0;
    search_ch	= 0;

    string_ptr = (const char *) arg_string;
    substr_ptr = string_ptr;

    cursor_ptr = string_ptr;
    cursor_end = string_ptr;

    string_end = string_ptr + strlen( arg_string );
    substr_end = string_end;

    for (;;) {
	nocase = 0;
	digits = 0;
	direct = 0;
	incomp = 0;
	invert = 0;
	search = 0;
	simmed = 0;
	spaces = 0;
	repeat = 1;

	for (;;) {
	    control_ch = * arg_control ++;
	    if ( ! control_ch ) goto control_end;

	    switch ( control_ch ) {

	    case '^':
		cursor_ptr = string_ptr;
		cursor_end = string_ptr;
		break;

	    case '$':
		cursor_ptr = string_end;
		cursor_end = string_end;
		break;

	    case '[':
		substr_ptr = cursor_ptr;
		break;

	    case '(':
		substr_ptr = cursor_end;
		break;

	    case '{':
		substr_ptr = cursor_end;
		while ( substr_ptr < string_end && isspace( * substr_ptr ) ) ++ substr_ptr;
		break;

	    case '}':
		substr_end = cursor_ptr;
		while ( -- substr_end >= string_ptr && isspace( * substr_end ) );
		++ substr_end;
		break;

	    case ')':
		substr_end = cursor_ptr;
		break;

	    case ']':
		substr_end = cursor_end;
		break;

	    case 'w':
	    case 'W':
		spaces = 1;
		break;

	    case 'n':
	    case 'N':
		search = 'N';
		break;

	    case ',':
	    case '.':
	    case '/':
	    case ':':
	    case ';':
	    case '@':
	    case '|':
		search = 'C';
		search_ch = control_ch;
		break;

	    case '=':
		if ( ! * arg_control ) return ~0;
		search = 'C';
		search_ch = * arg_control ++;
		break;

	    case 'c':
	    case 'C':
		search = 'C';
		search_ch = va_arg( arg_va_list, int );
		break;

	    case 'l':
	    case 'L':
		search = 'L';
		search_ptr = va_arg( arg_va_list, const char * );
		break;

	    case 't':
	    case 'T':
		search = 'T';
		search_ptr = va_arg( arg_va_list, const char * );
		break;

	    case 'x':
	    case 'X':
		invert = 1;
		break;

	    case 's':
	    case 'S':
		search = 'S';
		search_ptr = va_arg( arg_va_list, const char * );
		search_len = strlen( search_ptr );
		break;

	    case 'm':
	    case 'M':
		search = 'M';
		search_ptr = va_arg( arg_va_list, const char * );
		search_len = va_arg( arg_va_list, size_t );
		break;

	    case 'i':
	    case 'I':
		nocase = 1;
		break;

	    case '&':
		incomp = 1;
		break;

	    case '*':
		incomp = 2;
		break;

	    case '?':
		simmed = 1;
		break;

	    case '#':
		repeat = va_arg( arg_va_list, int );
		digits = 0;
		break;

	    case '>':
		direct = 1;
		break;

	    case '<':
		direct = -1;
		break;

	    case '0':
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	    case '8':
	    case '9':
		if ( digits == 0 ) {
		    repeat = control_ch - '0';
		    digits = 1;
		} else {
		    repeat *= 10;
		    repeat += control_ch - '0';
		    // ++ digits;
		}
		break;

	    default:
		return ~0;
	    }

	    if ( direct == 0 ) continue;

	    if ( repeat < 0 ) {
		direct = - direct;
		repeat = - repeat;
	    }

	    find_ptr = cursor_ptr;
	    find_end = cursor_end;

	    //--------------------------------------------------------------
	    // If no search type specified, maybe incremental or whitespace.
	    //--------------------------------------------------------------
	    if ( search == 0 ) {

		//--------------------------------------------------------------
		// If no whitespace option, then increment by single characters.
		//--------------------------------------------------------------
		if ( spaces == 0 ) {
		    if ( repeat <= 0 ) goto delimiter_found;	//-- if no delimiters to search
		    if ( direct > 0 ) {				//-- forward direction
			find_ptr += repeat;			//-- search for Nth delimiter
			if ( find_ptr >= string_end ) {		//-- if not enough
			    find_end = find_ptr = string_end;
			    goto delimiter_not_found;
			}
		    } else {					//-- reverse direction
			find_ptr -= repeat;			//-- search for Nth delimiter
			if ( find_ptr < string_ptr ) {		//-- if not enough
			    find_end = find_ptr = string_ptr;
			    goto delimiter_not_found;
			}
		    }
		    find_end = find_ptr;
		    goto delimiter_found;
		}

		//-------------------------------
		// Search for a whitespace group.
		//-------------------------------
		if ( direct > 0 ) {				//-- forward direction
		    if ( ( scan_ptr = cursor_end ) >= string_end ) goto delimiter_not_found;
		    if ( simmed && isspace( * scan_ptr ) ) {	//-- if skip immediate delimiter
			find_ptr = scan_ptr;
			while ( ++ scan_ptr < string_end && isspace( * scan_ptr ) );
			find_end = scan_ptr;
		    }
		    while ( repeat -- > 0 ) {			//-- search for Nth delimiter
			for (;;) {
			    if ( ++ scan_ptr >= string_end ) goto delimiter_not_found;
			    if ( isspace( * scan_ptr ) ) break;
			}
			find_ptr = scan_ptr;
			while ( ++ scan_ptr < string_end && isspace( * scan_ptr ) );
			find_end = scan_ptr;
		    }
		} else {					//-- reverse direction
		    if ( ( scan_ptr = cursor_ptr - 1 ) < string_ptr ) goto delimiter_not_found;
		    if ( simmed && isspace( * scan_ptr ) ) {	//-- if skip immediate delimiter
			find_end = scan_ptr + 1;
			while ( -- scan_ptr >= string_ptr && isspace( * scan_ptr ) );
			find_ptr = scan_ptr + 1;
		    }
		    while ( repeat -- > 0 ) {			//-- search for Nth delimiter
			for (;;) {
			    if ( -- scan_ptr < string_ptr ) goto delimiter_not_found;
			    if ( isspace( * scan_ptr ) ) break;
			}
			find_end = scan_ptr + 1;
			while ( -- scan_ptr >= string_ptr && isspace( * scan_ptr ) );
			find_ptr = scan_ptr + 1;
		    }
		}
		goto delimiter_found;
	    }

	    switch ( search ) {

	    case 'N':
		//----------------------------------------
		// Search for single newlines or newline
		// pairs with exactly one carriage return.
		//----------------------------------------
		if ( direct > 0 ) {				//-- forward direction
		    int code;
		    if ( ( scan_ptr = cursor_end ) >= string_end ) goto delimiter_not_found;
		    if ( simmed && ( code = newline_table[ 0xffU & * scan_ptr ] ) ) {
			find_ptr = scan_ptr;
			if ( ( code | newline_table[ 0xffU & * ++ scan_ptr ] ) != 3 ) -- scan_ptr;
			find_end = scan_ptr;
		    }
		    while ( repeat -- > 0 ) {			//-- search for Nth delimiter
			for (;;) {
			    if ( ++ scan_ptr >= string_end ) goto delimiter_not_found;
			    if ( ( code = newline_table[ 0xffU & * scan_ptr ] ) ) break;
			}
			find_ptr = scan_ptr;
			if ( ( code | newline_table[ 0xffU & * ++ scan_ptr ] ) != 3 ) -- scan_ptr;
			find_end = scan_ptr;
		    }
		} else {					//-- reverse direction
		    int code;
		    if ( ( scan_ptr = cursor_ptr - 1 ) < string_ptr ) goto delimiter_not_found;
		    if ( simmed && ( code = newline_table[ 0xffU & * scan_ptr ] ) ) {
			find_end = scan_ptr + 1;
			if ( ( code | newline_table[ 0xffU & * -- scan_ptr ] ) != 3 ) ++ scan_ptr;
			find_ptr = scan_ptr + 1;
		    }
		    while ( repeat -- > 0 ) {			//-- search for Nth delimiter
			for (;;) {
			    if ( -- scan_ptr < string_ptr ) goto delimiter_not_found;
			    if ( ( code = newline_table[ 0xffU & * scan_ptr ] ) ) break;
			}
			find_end = scan_ptr + 1;
			if ( ( code | newline_table[ 0xffU & * -- scan_ptr ] ) != 3 ) ++ scan_ptr;
			find_ptr = scan_ptr + 1;
		    }
		}
		goto delimiter_found;

	    case 'C':
		//------------------------------------
		// Search for a single character code.
		//------------------------------------
		if ( direct > 0 ) {				//-- forward direction
		    if ( ( scan_ptr = cursor_end ) >= string_end ) goto delimiter_not_found;
		    if ( simmed && * scan_ptr == search_ch ) {
			find_ptr = scan_ptr;
			find_end = ++ scan_ptr;
		    }
		    while ( repeat -- > 0 ) {			//-- search for Nth delimiter
			for (;;) {
			    if ( scan_ptr >= string_end ) goto delimiter_not_found;
			    if ( * scan_ptr == search_ch ) break;
			    ++ scan_ptr;
			}
			find_ptr = scan_ptr;
			find_end = ++ scan_ptr;
		    }
		} else {					//-- reverse direction
		    if ( ( scan_ptr = cursor_ptr - 1 ) < string_ptr ) goto delimiter_not_found;
		    if ( simmed && * scan_ptr == search_ch ) {
			find_ptr = scan_ptr;
			find_end = scan_ptr + 1;
			-- scan_ptr;
		    }
		    while ( repeat -- > 0 ) {			//-- search for Nth delimiter
			for (;;) {
			    if ( scan_ptr < string_ptr ) goto delimiter_not_found;
			    if ( * scan_ptr == search_ch ) break;
			    -- scan_ptr;
			}
			find_ptr = scan_ptr;
			find_end = scan_ptr + 1;
			-- scan_ptr;
		    }
		}
		goto delimiter_found;

	    case 'L':
		//------------------------------
		// Search for character in list.
		//------------------------------
		if ( invert ) {					//-- delimiters are NOT in the list
		    if ( direct > 0 ) {				//-- forward direction
			if ( ( scan_ptr = cursor_end ) >= string_end ) goto delimiter_not_found;
			if ( simmed && ! memchr( search_ptr, * scan_ptr, search_len ) ) {
			    find_ptr = scan_ptr;
			    find_end = ++ scan_ptr;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr >= string_end ) goto delimiter_not_found;
				if ( ! memchr( search_ptr, * scan_ptr, search_len ) ) break;
				++ scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    find_end = ++ scan_ptr;
			}
		    } else {					//-- reverse direction
			if ( ( scan_ptr = cursor_ptr - 1 ) < string_ptr ) goto delimiter_not_found;
			if ( simmed && ! memchr( search_ptr, * scan_ptr, search_len ) ) {
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + 1;
			    -- scan_ptr;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr < string_ptr ) goto delimiter_not_found;
				if ( ! memchr( search_ptr, * scan_ptr, search_len ) ) break;
				-- scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + 1;
			    -- scan_ptr;
			}
		    }
		} else {					//-- delimters are IN the list
		    if ( direct > 0 ) {				//-- forward direction
			if ( ( scan_ptr = cursor_end ) >= string_end ) goto delimiter_not_found;
			if ( simmed && memchr( search_ptr, * scan_ptr, search_len ) ) {
			    find_ptr = scan_ptr;
			    find_end = ++ scan_ptr;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr >= string_end ) goto delimiter_not_found;
				if ( memchr( search_ptr, * scan_ptr, search_len ) ) break;
				++ scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    find_end = ++ scan_ptr;
			}
		    } else {					//-- reverse direction
			if ( ( scan_ptr = cursor_ptr - 1 ) < string_ptr ) goto delimiter_not_found;
			if ( simmed && memchr( search_ptr, * scan_ptr, search_len ) ) {
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + 1;
			    -- scan_ptr;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr < string_ptr ) goto delimiter_not_found;
				if ( memchr( search_ptr, * scan_ptr, search_len ) ) break;
				-- scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + 1;
			    -- scan_ptr;
			}
		    }
		}
		goto delimiter_found;

	    case 'T':
		//-------------------------------
		// Search for character in table.
		//-------------------------------
		if ( invert ) {					//-- delimiters are NOT in the list
		    if ( direct > 0 ) {				//-- forward direction
			if ( ( scan_ptr = cursor_end ) >= string_end ) goto delimiter_not_found;
			if ( simmed && ! search_ptr[ 0xffU & * scan_ptr ] ) {
			    find_ptr = scan_ptr;
			    find_end = ++ scan_ptr;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr >= string_end ) goto delimiter_not_found;
				if ( ! search_ptr[ 0xffU & * scan_ptr ] ) break;
				++ scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    find_end = ++ scan_ptr;
			}
		    } else {					//-- reverse direction
			if ( ( scan_ptr = cursor_ptr - 1 ) < string_ptr ) goto delimiter_not_found;
			if ( simmed && ! search_ptr[ 0xffU & * scan_ptr ] ) {
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + 1;
			    -- scan_ptr;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr < string_ptr ) goto delimiter_not_found;
				if ( ! search_ptr[ 0xffU & * scan_ptr ] ) break;
				-- scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + 1;
			    -- scan_ptr;
			}
		    }
		} else {					//-- delimters are IN the list
		    if ( direct > 0 ) {				//-- forward direction
			if ( ( scan_ptr = cursor_end ) >= string_end ) goto delimiter_not_found;
			if ( simmed && search_ptr[ 0xffU & * scan_ptr ] ) {
			    find_ptr = scan_ptr;
			    find_end = ++ scan_ptr;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr >= string_end ) goto delimiter_not_found;
				if ( search_ptr[ 0xffU & * scan_ptr ] ) break;
				++ scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    find_end = ++ scan_ptr;
			}
		    } else {					//-- reverse direction
			if ( ( scan_ptr = cursor_ptr - 1 ) < string_ptr ) goto delimiter_not_found;
			if ( simmed && search_ptr[ 0xffU & * scan_ptr ] ) {
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + 1;
			    -- scan_ptr;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr < string_ptr ) goto delimiter_not_found;
				if ( search_ptr[ 0xffU & * scan_ptr ] ) break;
				-- scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + 1;
			    -- scan_ptr;
			}
		    }
		}
		goto delimiter_found;

	    case 'S':
		//-------------------
		// Search for string.
		//-------------------
		if ( nocase ) {					//-- case insensitive
		    if ( direct > 0 ) {				//-- forward direction
			scan_ptr = cursor_end;
			if ( scan_ptr + search_len > string_end ) goto delimiter_not_found;
			if ( simmed && strncasecmp( scan_ptr, search_ptr, search_len ) == 0 ) {
			    find_ptr = scan_ptr;
			    scan_ptr += search_len;
			    find_end = scan_ptr;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr + search_len > string_end ) goto delimiter_not_found;
				if ( strncasecmp( scan_ptr, search_ptr, search_len ) == 0 ) break;
				++ scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    scan_ptr += search_len;
			    find_end = scan_ptr;
			}
		    } else {					//-- reverse direction
			scan_ptr = cursor_ptr - search_len;
			if ( scan_ptr < string_ptr ) goto delimiter_not_found;
			if ( simmed && strncasecmp( scan_ptr, search_ptr, search_len ) == 0 ) {
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + search_len;
			    scan_ptr -= search_len;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr < string_ptr ) goto delimiter_not_found;
				if ( strncasecmp( scan_ptr, search_ptr, search_len ) == 0 ) break;
				-- scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + search_len;
			    scan_ptr -= search_len;
			}
		    }
		} else {					//-- case sensitive
		    if ( direct > 0 ) {				//-- forward direction
			scan_ptr = cursor_end;
			if ( scan_ptr + search_len > string_end ) goto delimiter_not_found;
			if ( simmed && strncmp( scan_ptr, search_ptr, search_len ) == 0 ) {
			    find_ptr = scan_ptr;
			    scan_ptr += search_len;
			    find_end = scan_ptr;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr + search_len > string_end ) goto delimiter_not_found;
				if ( strncmp( scan_ptr, search_ptr, search_len ) == 0 ) break;
				++ scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    scan_ptr += search_len;
			    find_end = scan_ptr;
			}
		    } else {					//-- reverse direction
			scan_ptr = cursor_ptr - search_len;
			if ( scan_ptr < string_ptr ) goto delimiter_not_found;
			if ( simmed && strncmp( scan_ptr, search_ptr, search_len ) == 0 ) {
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + search_len;
			    scan_ptr -= search_len;
			}
			while ( repeat -- > 0 ) {		//-- search for Nth delimiter
			    for (;;) {
				if ( scan_ptr < string_ptr ) goto delimiter_not_found;
				if ( strncmp( scan_ptr, search_ptr, search_len ) == 0 ) break;
				-- scan_ptr;
			    }
			    find_ptr = scan_ptr;
			    find_end = scan_ptr + search_len;
			    scan_ptr -= search_len;
			}
		    }
		}
		goto delimiter_found;

	    default:
		return ~0;
	    }

	delimiter_found:
	    cursor_ptr = find_ptr;
	    cursor_end = find_end;
	    break;

	delimiter_not_found:
	    if ( incomp == 2 ) {
		cursor_ptr = find_ptr;
		cursor_end = find_end;
	    }
	    else if ( incomp == 0 ) {
		cursor_ptr = scan_ptr;
		cursor_end = scan_ptr;
	    }
	    break;
	}
    }

 control_end:

    if ( substr_end < substr_ptr ) substr_end = substr_ptr;

    if ( arg_ss_ptr_p ) * arg_ss_ptr_p = (char *) substr_ptr;
    if ( arg_ss_end_p ) * arg_ss_end_p = (char *) substr_end;

    return substr_end - substr_ptr;
}

