//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
// file		strselect.c
//
// program	strselect
//
// compile	gcc -O2 -o strselect strselect.c
//
// purpose	Shell script tool for selecting substrings of a given string
//		based on a command set specified in a control string which can
//		use command line arguments, and outputs the resultant string
//		suitable for shell capture.
//
// syntax	strselect 'commandstring' "inputstring" [ args ... ]
//
// 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 in the control string.
//
//	c C	Specify the next search is for a single character given in an
//		argument.
//
//	l L	Specify the next search is for a single character in a list of
//		characters given in an argument.
//
//	x X	When used with L, specifies that the test for the delimiter
//		character is inverted, that is, the first character NOT in the
//		list.
//
//	s S	Specify the next search is for a string given in an argument
//
//	i I	When used with S, 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.
//
//	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.
//
//
// examples:
//
// To remove the directory path and extension from a file name:
//	strselect "${fullname}" '$1c<($1c<)' "/" "."
//	strselect "${fullname}" '$/<($1.<)'
//
// To extract the directory from a path (aka dirname):
//	strselect "${fullname}" '$1c<)' "/"
//	strselect "${fullname}" '$/<)'
//
// To extract the base name from a path (aka basename):
//	strselect "${fullname}" '$1c<(' "/"
//	strselect "${fullname}" '$/<)'
//
// To extract the username part of an email address:
//	strselect "${email}" '^1c>)' "@"
//	strselect "${email}" '^@>)'
//
// To extract the hostname part of an email address:
//	strselect "${email}" '^1c>(' "@"
//	strselect "${email}" '^@>('
//
// To extract the second level domain from a fully qualified domain name:
//	strselect "${fqdn}" '$2c<(' "."
//	strselect "${fqdn}" '$2.<('
//
// To extract an email address from within <>:
//	strselect "${email}" '$c!>(^c!<)'
//
// note		In csh/tcsh scripts, the ! character will have to be escaped
//		like \! even when quoted in single quotes.
//-----------------------------------------------------------------------------

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

static unsigned char	newline_table	[256]	= {
//                          N V F C
//                          L T F R
	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 };

//-----------------------------------------------------------------------------
// function	main
//-----------------------------------------------------------------------------
int
main (
    int					argc
    ,
    const char * const *		argv
    )
{
    const char * const *	main_argv	;

    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	;

    const char *		control_ptr	;
    const char *		program_name	;

    size_t				search_len	;

    int					main_argc	;

    int					control_ch	;
    int					search_ch	;

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


    main_argc = argc;
    main_argv = (const char * const *) argv;

    program_name = scan_ptr = * main_argv;
    while ( * scan_ptr ) if ( * scan_ptr ++ == '/' && * scan_ptr ) program_name = scan_ptr;

    if ( main_argc <= 1 ) {
	fprintf( stderr, "%s: missing input string\n", program_name );
    }
    if ( main_argc <= 2 ) {
	fprintf( stderr, "%s: missing command string\n", program_name );
	return 1;
    }

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

    -- main_argc;
    ++ main_argv;
    control_ptr = * main_argv;

    -- main_argc;
    ++ main_argv;
    string_ptr = * main_argv;
    substr_ptr = string_ptr;
    cursor_ptr = string_ptr;
    cursor_end = string_ptr;
    string_end = string_ptr + strlen( string_ptr );
    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 = * control_ptr ++;
	    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 ( ! * control_ptr ) return ~0;
		search = 'C';
		search_ch = * control_ptr ++;
		break;

	    case 'c':
	    case 'C':
		search = 'C';
		if ( -- main_argc <= 0 ) goto out_of_args;
		search_ch = * * ++ main_argv;
		break;

	    case 'l':
	    case 'L':
		search = 'L';
		if ( -- main_argc <= 0 ) goto out_of_args;
		search_ptr = * ++ main_argv;
		break;

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

	    case 's':
	    case 'S':
		search = 'S';
		if ( -- main_argc <= 0 ) goto out_of_args;
		search_ptr = * ++ main_argv;
		search_len = strlen( search_ptr );
		break;

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

	    case '&':
		incomp = 1;
		break;

	    case '*':
		incomp = 2;
		break;

	    case '?':
		simmed = 1;
		break;

	    case '#':
		if ( -- main_argc <= 0 ) goto out_of_args;
		repeat = (int) strtol( (const char *) * ++ main_argv, NULL, 0 );
		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 '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;
    while ( substr_ptr < substr_end ) {
	putc( * substr_ptr, stdout );
	++ substr_ptr;
    }
    putc( '\n', stdout );
    return 0;

 out_of_args:
    fprintf( stderr,
	     "%s: out of arguments for '%c' at position %d in \"%s\"\n",
	     program_name,
	     control_ch,
	     control_ptr - (const char *) ( argv[2] ),
	     argv[2] );
    return 1;
}

