/*
 * regex(3) test harness
 *
 * build:	cc -o testregex testregex.c
 * help:	testregex --man
 * note:	REG_* features are detected by #ifdef; if REG_* are enums
 *		then supply #define REG_foo REG_foo for each enum REG_foo
 *
 *	Glenn Fowler <gsf@research.att.com>
 *	AT&T Research
 *
 * PLEASE: publish your tests so everyone can benefit
 *
 * The following license covers testregex.c and all associated test data.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of THIS SOFTWARE FILE (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, and/or sell copies of the
 * Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following disclaimer:
 *
 * THIS SOFTWARE IS PROVIDED BY AT&T ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL AT&T BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

static const char id[] = "\n@(#)$Id: testregex (AT&T Research) 2008-05-15 $\0\n";

#if _PACKAGE_ast
#include <ast.h>
#else
#include <sys/types.h>
#endif

#include <ctype.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "./regex.h"

#ifdef __STDC__
#include <locale.h>
#include <stdlib.h>
#endif

#if ! _PACKAGE_ast
#undef REG_DISCIPLINE
#endif

#ifndef REG_DELIMITED
#undef _REG_subcomp
#endif

#define TEST_ARE 0x00000001
#define TEST_BRE 0x00000002
#define TEST_ERE 0x00000004
#define TEST_KRE 0x00000008
#define TEST_LRE 0x00000010
#define TEST_SRE 0x00000020

#define TEST_EXPAND 0x00000040
#define TEST_LENIENT 0x00000080

#define TEST_QUERY 0x00000100
#define TEST_SUB 0x00000200
#define TEST_UNSPECIFIED 0x00000400
#define TEST_VERIFY 0x00000800
#define TEST_AND 0x00001000
#define TEST_OR 0x00002000

#define TEST_DELIMIT 0x00010000
#define TEST_OK 0x00020000
#define TEST_SAME 0x00040000

#define TEST_ACTUAL 0x00100000
#define TEST_BASELINE 0x00200000
#define TEST_FAIL 0x00400000
#define TEST_PASS 0x00800000
#define TEST_SUMMARY 0x01000000

#define TEST_IGNORE_ERROR 0x02000000
#define TEST_IGNORE_OVER 0x04000000
#define TEST_IGNORE_POSITION 0x08000000

#define TEST_CATCH 0x10000000
#define TEST_VERBOSE 0x20000000

#define TEST_DECOMP 0x40000000

#define TEST_GLOBAL                                                                                                    \
    (TEST_ACTUAL | TEST_AND | TEST_BASELINE | TEST_CATCH | TEST_FAIL | TEST_IGNORE_ERROR | TEST_IGNORE_OVER |          \
     TEST_IGNORE_POSITION | TEST_OR | TEST_PASS | TEST_SUMMARY | TEST_VERBOSE)

#ifdef REG_DISCIPLINE


#include <stk.h>

typedef struct Disc_s {
    regdisc_t disc;
    int ordinal;
    Sfio_t* sp;
} Disc_t;

static void* compf(const regex_t* re, const char* xstr, size_t xlen, regdisc_t* disc) {
    Disc_t* dp = (Disc_t*)disc;

    return (void*)++dp->ordinal;
}

static int execf(const regex_t* re, void* data, const char* xstr, size_t xlen, const char* sstr, size_t slen,
                 char** snxt, regdisc_t* disc) {
    Disc_t* dp = (Disc_t*)disc;

    sfprintf(dp->sp, "{%-.*s}(%d:%d)", xlen, xstr, (int)data, slen);
    return atoi(xstr);
}

static void* resizef(void* handle, void* data, size_t size) {
    if ( ! size )
        return 0;
    return stkalloc((Sfio_t*)handle, size);
}

#endif

#ifndef NiL
#ifdef __STDC__
#define NiL 0
#else
#define NiL (char*)0
#endif
#endif

#define H(x)                                                                                                           \
    do {                                                                                                               \
        if ( html )                                                                                                    \
            fprintf(stderr, x);                                                                                        \
    } while ( 0 )
#define T(x) fprintf(stderr, x)

static void help(int html) {
    H("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n");
    H("<HTML>\n");
    H("<HEAD>\n");
    H("<TITLE>testregex man document</TITLE>\n");
    H("</HEAD>\n");
    H("<BODY bgcolor=white>\n");
    H("<PRE>\n");
    T("NAME\n");
    T("  testregex - regex(3) test harness\n");
    T("\n");
    T("SYNOPSIS\n");
    T("  testregex [ options ]\n");
    T("\n");
    T("DESCRIPTION\n");
    T("  testregex reads regex(3) test specifications, one per line, from the\n");
    T("  standard input and writes one output line for each failed test. A\n");
    T("  summary line is written after all tests are done. Each successful\n");
    T("  test is run again with REG_NOSUB. Unsupported features are noted\n");
    T("  before the first test, and tests requiring these features are\n");
    T("  silently ignored.\n");
    T("\n");
    T("OPTIONS\n");
    T("  -c	catch signals and non-terminating calls\n");
    T("  -e	ignore error return mismatches\n");
    T("  -h	list help on standard error\n");
    T("  -n	do not repeat successful tests with regnexec()\n");
    T("  -o	ignore match[] overrun errors\n");
    T("  -p	ignore negative position mismatches\n");
    T("  -s	use stack instead of malloc\n");
    T("  -x	do not repeat successful tests with REG_NOSUB\n");
    T("  -v	list each test line\n");
    T("  -A	list failed test lines with actual answers\n");
    T("  -B	list all test lines with actual answers\n");
    T("  -F	list failed test lines\n");
    T("  -P	list passed test lines\n");
    T("  -S	output one summary line\n");
    T("\n");
    T("INPUT FORMAT\n");
    T("  Input lines may be blank, a comment beginning with #, or a test\n");
    T("  specification. A specification is five fields separated by one\n");
    T("  or more tabs. NULL denotes the empty string and NIL denotes the\n");
    T("  0 pointer.\n");
    T("\n");
    T("  Field 1: the regex(3) flags to apply, one character per REG_feature\n");
    T("  flag. The test is skipped if REG_feature is not supported by the\n");
    T("  implementation. If the first character is not [BEASKL] then the\n");
    T("  specification is a global control line. One or more of [BEASKL] may be\n");
    T("  specified; the test will be repeated for each mode.\n");
    T("\n");
    T("    B 	basic			BRE	(grep, ed, sed)\n");
    T("    E 	REG_EXTENDED		ERE	(egrep)\n");
    T("    A	REG_AUGMENTED		ARE	(egrep with negation)\n");
    T("    S	REG_SHELL		SRE	(sh glob)\n");
    T("    K	REG_SHELL|REG_AUGMENTED	KRE	(ksh glob)\n");
    T("    L	REG_LITERAL		LRE	(fgrep)\n");
    T("\n");
    T("    a	REG_LEFT|REG_RIGHT	implicit ^...$\n");
    T("    b	REG_NOTBOL		lhs does not match ^\n");
    T("    c	REG_COMMENT		ignore space and #...\\n\n");
    T("    d	REG_SHELL_DOT		explicit leading . match\n");
    T("    e	REG_NOTEOL		rhs does not match $\n");
    T("    f	REG_MULTIPLE		multiple \\n separated patterns\n");
    T("    g	FNM_LEADING_DIR		testfnmatch only -- match until /\n");
    T("    h	REG_MULTIREF		multiple digit backref\n");
    T("    i	REG_ICASE		ignore case\n");
    T("    j	REG_SPAN		. matches \\n\n");
    T("    k	REG_ESCAPE		\\ to ecape [...] delimiter\n");
    T("    l	REG_LEFT		implicit ^...\n");
    T("    m	REG_MINIMAL		minimal match\n");
    T("    n	REG_NEWLINE		explicit \\n match\n");
    T("    o	REG_ENCLOSED		(|&) magic inside [@|&](...)\n");
    T("    p	REG_SHELL_PATH		explicit / match\n");
    T("    q	REG_DELIMITED		delimited pattern\n");
    T("    r	REG_RIGHT		implicit ...$\n");
    T("    s	REG_SHELL_ESCAPED	\\ not special\n");
    T("    t	REG_MUSTDELIM		all delimiters must be specified\n");
    T("    u	standard unspecified behavior -- errors not counted\n");
    T("    w	REG_NOSUB		no subexpression match array\n");
    T("    x	REG_LENIENT		let some errors slide\n");
    T("    y	REG_LEFT		regexec() implicit ^...\n");
    T("    z	REG_NULL		NULL subexpressions ok\n");
    T("    $	                        expand C \\c escapes in fields 2 and 3\n");
    T("    /	                        field 2 is a regsubcomp() expression\n");
    T("    =	                        field 3 is a regdecomp() expression\n");
    T("\n");
    T("  Field 1 control lines:\n");
    T("\n");
    T("    C		set LC_COLLATE and LC_CTYPE to locale in field 2\n");
    T("\n");
    T("    ?test ...	output field 5 if passed and != EXPECTED, silent otherwise\n");
    T("    &test ...	output field 5 if current and previous passed\n");
    T("    |test ...	output field 5 if current passed and previous failed\n");
    T("    ; ...	output field 2 if previous failed\n");
    T("    {test ...	skip if failed until }\n");
    T("    }		end of skip\n");
    T("\n");
    T("    : comment		comment copied as output NOTE\n");
    T("    :comment:test	:comment: ignored\n");
    T("    N[OTE] comment	comment copied as output NOTE\n");
    T("    T[EST] comment	comment\n");
    T("\n");
    T("    number		use number for nmatch (20 by default)\n");
    T("\n");
    T("  Field 2: the regular expression pattern; SAME uses the pattern from\n");
    T("    the previous specification.\n");
    T("\n");
    T("  Field 3: the string to match.\n");
    T("\n");
    T("  Field 4: the test outcome. This is either one of the posix error\n");
    T("    codes (with REG_ omitted) or the match array, a list of (m,n)\n");
    T("    entries with m and n being first and last+1 positions in the\n");
    T("    field 3 string, or NULL if REG_NOSUB is in effect and success\n");
    T("    is expected. BADPAT is acceptable in place of any regcomp(3)\n");
    T("    error code. The match[] array is initialized to (-2,-2) before\n");
    T("    each test. All array elements from 0 to nmatch-1 must be specified\n");
    T("    in the outcome. Unspecified endpoints (offset -1) are denoted by ?.\n");
    T("    Unset endpoints (offset -2) are denoted by X. {x}(o:n) denotes a\n");
    T("    matched (?{...}) expression, where x is the text enclosed by {...},\n");
    T("    o is the expression ordinal counting from 1, and n is the length of\n");
    T("    the unmatched portion of the subject string. If x starts with a\n");
    T("    number then that is the return value of re_execf(), otherwise 0 is\n");
    T("    returned.\n");
    T("\n");
    T("  Field 5: optional comment appended to the report.\n");
    T("\n");
    T("CAVEAT\n");
    T("    If a regex implementation misbehaves with memory then all bets are off.\n");
    T("\n");
    T("CONTRIBUTORS\n");
    T("  Glenn Fowler    gsf@research.att.com        (ksh strmatch, regex extensions)\n");
    T("  David Korn      dgk@research.att.com        (ksh glob matcher)\n");
    T("  Doug McIlroy    mcilroy@dartmouth.edu       (ast regex/testre in C++)\n");
    T("  Tom Lord        lord@regexps.com            (rx tests)\n");
    T("  Henry Spencer   henry@zoo.toronto.edu       (original public regex)\n");
    T("  Andrew Hume     andrew@research.att.com     (gre tests)\n");
    T("  John Maddock    John_Maddock@compuserve.com (regex++ tests)\n");
    T("  Philip Hazel    ph10@cam.ac.uk              (pcre tests)\n");
    T("  Ville Laurikari vl@iki.fi                   (libtre tests)\n");
    H("</PRE>\n");
    H("</BODY>\n");
    H("</HTML>\n");
}

#ifndef elementsof
#define elementsof(x) (sizeof(x) / sizeof(x[0]))
#endif

#ifndef streq
#define streq(a, b) (*(a) == *(b) && ! strcmp(a, b))
#endif

#define HUNG 2
#define NOTEST (~0)

#ifndef REG_TEST_DEFAULT
#define REG_TEST_DEFAULT 0
#endif

#ifndef REG_EXEC_DEFAULT
#define REG_EXEC_DEFAULT 0
#endif

static const char* unsupported[] = {"BASIC",
#ifndef REG_EXTENDED
                                    "EXTENDED",
#endif
#ifndef REG_AUGMENTED
                                    "AUGMENTED",
#endif
#ifndef REG_SHELL
                                    "SHELL",
#endif

#ifndef REG_COMMENT
                                    "COMMENT",
#endif
#ifndef REG_DELIMITED
                                    "DELIMITED",
#endif
#ifndef REG_DISCIPLINE
                                    "DISCIPLINE",
#endif
#ifndef REG_ESCAPE
                                    "ESCAPE",
#endif
#ifndef REG_ICASE
                                    "ICASE",
#endif
#ifndef REG_LEFT
                                    "LEFT",
#endif
#ifndef REG_LENIENT
                                    "LENIENT",
#endif
#ifndef REG_LITERAL
                                    "LITERAL",
#endif
#ifndef REG_MINIMAL
                                    "MINIMAL",
#endif
#ifndef REG_MULTIPLE
                                    "MULTIPLE",
#endif
#ifndef REG_MULTIREF
                                    "MULTIREF",
#endif
#ifndef REG_MUSTDELIM
                                    "MUSTDELIM",
#endif
#ifndef REG_NEWLINE
                                    "NEWLINE",
#endif
#ifndef REG_NOTBOL
                                    "NOTBOL",
#endif
#ifndef REG_NOTEOL
                                    "NOTEOL",
#endif
#ifndef REG_NULL
                                    "NULL",
#endif
#ifndef REG_RIGHT
                                    "RIGHT",
#endif
#ifndef REG_SHELL_DOT
                                    "SHELL_DOT",
#endif
#ifndef REG_SHELL_ESCAPED
                                    "SHELL_ESCAPED",
#endif
#ifndef REG_SHELL_GROUP
                                    "SHELL_GROUP",
#endif
#ifndef REG_SHELL_PATH
                                    "SHELL_PATH",
#endif
#ifndef REG_SPAN
                                    "SPAN",
#endif
#if REG_NOSUB & REG_TEST_DEFAULT
                                    "SUBMATCH",
#endif
#if ! _REG_nexec
                                    "regnexec",
#endif
#if ! _REG_subcomp
                                    "regsubcomp",
#endif
#if ! _REG_decomp
                                    "redecomp",
#endif
                                    0};

#ifndef REG_COMMENT
#define REG_COMMENT NOTEST
#endif
#ifndef REG_DELIMITED
#define REG_DELIMITED NOTEST
#endif
#ifndef REG_ESCAPE
#define REG_ESCAPE NOTEST
#endif
#ifndef REG_ICASE
#define REG_ICASE NOTEST
#endif
#ifndef REG_LEFT
#define REG_LEFT NOTEST
#endif
#ifndef REG_LENIENT
#define REG_LENIENT 0
#endif
#ifndef REG_MINIMAL
#define REG_MINIMAL NOTEST
#endif
#ifndef REG_MULTIPLE
#define REG_MULTIPLE NOTEST
#endif
#ifndef REG_MULTIREF
#define REG_MULTIREF NOTEST
#endif
#ifndef REG_MUSTDELIM
#define REG_MUSTDELIM NOTEST
#endif
#ifndef REG_NEWLINE
#define REG_NEWLINE NOTEST
#endif
#ifndef REG_NOTBOL
#define REG_NOTBOL NOTEST
#endif
#ifndef REG_NOTEOL
#define REG_NOTEOL NOTEST
#endif
#ifndef REG_NULL
#define REG_NULL NOTEST
#endif
#ifndef REG_RIGHT
#define REG_RIGHT NOTEST
#endif
#ifndef REG_SHELL_DOT
#define REG_SHELL_DOT NOTEST
#endif
#ifndef REG_SHELL_ESCAPED
#define REG_SHELL_ESCAPED NOTEST
#endif
#ifndef REG_SHELL_GROUP
#define REG_SHELL_GROUP NOTEST
#endif
#ifndef REG_SHELL_PATH
#define REG_SHELL_PATH NOTEST
#endif
#ifndef REG_SPAN
#define REG_SPAN NOTEST
#endif

#define REG_UNKNOWN (-1)

#ifndef REG_ENEWLINE
#define REG_ENEWLINE (REG_UNKNOWN - 1)
#endif
#ifndef REG_ENULL
#ifndef REG_EMPTY
#define REG_ENULL (REG_UNKNOWN - 2)
#else
#define REG_ENULL REG_EMPTY
#endif
#endif
#ifndef REG_ECOUNT
#define REG_ECOUNT (REG_UNKNOWN - 3)
#endif
#ifndef REG_BADESC
#define REG_BADESC (REG_UNKNOWN - 4)
#endif
#ifndef REG_EMEM
#define REG_EMEM (REG_UNKNOWN - 5)
#endif
#ifndef REG_EHUNG
#define REG_EHUNG (REG_UNKNOWN - 6)
#endif
#ifndef REG_EBUS
#define REG_EBUS (REG_UNKNOWN - 7)
#endif
#ifndef REG_EFAULT
#define REG_EFAULT (REG_UNKNOWN - 8)
#endif
#ifndef REG_EFLAGS
#define REG_EFLAGS (REG_UNKNOWN - 9)
#endif
#ifndef REG_EDELIM
#define REG_EDELIM (REG_UNKNOWN - 9)
#endif

static const struct {
    int code;
    char* name;
} codes[] = {
    REG_UNKNOWN, "UNKNOWN", REG_NOMATCH, "NOMATCH", REG_BADPAT,   "BADPAT",   REG_ECOLLATE, "ECOLLATE",
    REG_ECTYPE,  "ECTYPE",  REG_EESCAPE, "EESCAPE", REG_ESUBREG,  "ESUBREG",  REG_EBRACK,   "EBRACK",
    REG_EPAREN,  "EPAREN",  REG_EBRACE,  "EBRACE",  REG_BADBR,    "BADBR",    REG_ERANGE,   "ERANGE",
    REG_ESPACE,  "ESPACE",  REG_BADRPT,  "BADRPT",  REG_ENEWLINE, "ENEWLINE", REG_ENULL,    "ENULL",
    REG_ECOUNT,  "ECOUNT",  REG_BADESC,  "BADESC",  REG_EMEM,     "EMEM",     REG_EHUNG,    "EHUNG",
    REG_EBUS,    "EBUS",    REG_EFAULT,  "EFAULT",  REG_EFLAGS,   "EFLAGS",   REG_EDELIM,   "EDELIM",
};

static struct {
    regmatch_t NOMATCH;
    int errors;
    int extracted;
    int ignored;
    int lineno;
    int passed;
    int signals;
    int unspecified;
    int verify;
    int warnings;
    char* file;
    char* stack;
    char* which;
    jmp_buf gotcha;
#ifdef REG_DISCIPLINE
    Disc_t disc;
#endif
} state;

static void quote(char* s, int len, unsigned long test) {
    unsigned char* u = (unsigned char*)s;
    unsigned char* e;
    int c;
#ifdef MB_CUR_MAX
    int w;
#endif

    if ( ! u )
        printf("NIL");
    else if ( ! *u && len <= 1 )
        printf("NULL");
    else if ( test & TEST_EXPAND ) {
        if ( len < 0 )
            len = strlen((char*)u);
        e = u + len;
        if ( test & TEST_DELIMIT )
            printf("\"");
        while ( u < e )
            switch ( c = *u++ ) {
                case '\\': printf("\\\\"); break;
                case '"':
                    if ( test & TEST_DELIMIT )
                        printf("\\\"");
                    else
                        printf("\"");
                    break;
                case '\a': printf("\\a"); break;
                case '\b': printf("\\b"); break;
                case 033: printf("\\e"); break;
                case '\f': printf("\\f"); break;
                case '\n': printf("\\n"); break;
                case '\r': printf("\\r"); break;
                case '\t': printf("\\t"); break;
                case '\v': printf("\\v"); break;
                default:
#ifdef MB_CUR_MAX
                    s = (char*)u - 1;
                    if ( (w = mblen(s, (char*)e - s)) > 1 ) {
                        u += w - 1;
                        fwrite(s, 1, w, stdout);
                    }
                    else
#endif
                        if ( ! iscntrl(c) && isprint(c) )
                        putchar(c);
                    else
                        printf("\\x%02x", c);
                    break;
            }
        if ( test & TEST_DELIMIT )
            printf("\"");
    }
    else
        printf("%s", s);
}

static void report(char* comment, char* fun, char* re, char* s, int len, char* msg, int flags, unsigned long test) {
    if ( state.file )
        printf("%s:", state.file);
    printf("%d:", state.lineno);
    if ( re ) {
        printf(" ");
        quote(re, -1, test | TEST_DELIMIT);
        if ( s ) {
            printf(" versus ");
            quote(s, len, test | TEST_DELIMIT);
        }
    }
    if ( test & TEST_UNSPECIFIED ) {
        state.unspecified++;
        printf(" unspecified behavior");
    }
    else
        state.errors++;
    if ( state.which )
        printf(" %s", state.which);
    if ( flags & REG_NOSUB )
        printf(" NOSUB");
    if ( fun )
        printf(" %s", fun);
    if ( comment[strlen(comment) - 1] == '\n' )
        printf(" %s", comment);
    else {
        printf(" %s: ", comment);
        if ( msg )
            printf("%s: ", msg);
    }
}

static void error(regex_t* preg, int code) {
    char* msg;
    char buf[256];

    switch ( code ) {
        case REG_EBUS: msg = "bus error"; break;
        case REG_EFAULT: msg = "memory fault"; break;
        case REG_EHUNG: msg = "did not terminate"; break;
        default: regerror(code, preg, msg = buf, sizeof buf); break;
    }
    printf("%s\n", msg);
}

static void bad(char* comment, char* re, char* s, int len, unsigned long test) {
    printf("bad test case ");
    report(comment, NiL, re, s, len, NiL, 0, test);
    exit(1);
}

static int escape(char* s) {
    char* b;
    char* t;
    char* q;
    char* e;
    int c;

    for ( b = t = s; (*t = *s); s++, t++ )
        if ( *s == '\\' )
            switch ( *++s ) {
                case '\\': break;
                case 'a': *t = '\a'; break;
                case 'b': *t = '\b'; break;
                case 'c':
                    if ( (*t = *++s) )
                        *t &= 037;
                    else
                        s--;
                    break;
                case 'e':
                case 'E': *t = 033; break;
                case 'f': *t = '\f'; break;
                case 'n': *t = '\n'; break;
                case 'r': *t = '\r'; break;
                case 's': *t = ' '; break;
                case 't': *t = '\t'; break;
                case 'v': *t = '\v'; break;
                case 'u':
                case 'x':
                    c = 0;
                    q = c == 'u' ? (s + 5) : (char*)0;
                    e = s + 1;
                    while ( ! e || ! q || s < q ) {
                        switch ( *++s ) {
                            case 'a':
                            case 'b':
                            case 'c':
                            case 'd':
                            case 'e':
                            case 'f': c = (c << 4) + *s - 'a' + 10; continue;
                            case 'A':
                            case 'B':
                            case 'C':
                            case 'D':
                            case 'E':
                            case 'F': c = (c << 4) + *s - 'A' + 10; continue;
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9': c = (c << 4) + *s - '0'; continue;
                            case '{':
                            case '[':
                                if ( s != e ) {
                                    s--;
                                    break;
                                }
                                e = 0;
                                continue;
                            case '}':
                            case ']':
                                if ( e )
                                    s--;
                                break;
                            default: s--; break;
                        }
                        break;
                    }
                    *t = c;
                    break;
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                    c = *s - '0';
                    q = s + 2;
                    while ( s < q ) {
                        switch ( *++s ) {
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7': c = (c << 3) + *s - '0'; break;
                            default: q = --s; break;
                        }
                    }
                    *t = c;
                    break;
                default: *(s + 1) = 0; bad("invalid C \\ escape\n", s - 1, NiL, 0, 0);
            }
    return t - b;
}

static void matchoffprint(int off) {
    switch ( off ) {
        case -2: printf("X"); break;
        case -1: printf("?"); break;
        default: printf("%d", off); break;
    }
}

static void matchprint(regmatch_t* match, int nmatch, int nsub, char* ans, unsigned long test) {
    int i;

    for ( ; nmatch > nsub + 1; nmatch-- )
        if ( (match[nmatch - 1].rm_so != -1 || match[nmatch - 1].rm_eo != -1) &&
             (! (test & TEST_IGNORE_POSITION) || (match[nmatch - 1].rm_so >= 0 && match[nmatch - 1].rm_eo >= 0)) )
            break;
    for ( i = 0; i < nmatch; i++ ) {
        printf("(");
        matchoffprint(match[i].rm_so);
        printf(",");
        matchoffprint(match[i].rm_eo);
        printf(")");
    }
    if ( ! (test & (TEST_ACTUAL | TEST_BASELINE)) ) {
        if ( ans )
            printf(" expected: %s", ans);
        printf("\n");
    }
}

static int matchcheck(regmatch_t* match, int nmatch, int nsub, char* ans, char* re, char* s, int len, int flags,
                      unsigned long test) {
    char* p;
    int i;
    int m;
    int n;

    if ( streq(ans, "OK") )
        return test & (TEST_BASELINE | TEST_PASS | TEST_VERIFY);
    for ( i = 0, p = ans; i < nmatch && *p; i++ ) {
        if ( *p == '{' ) {
#ifdef REG_DISCIPLINE
            char* x;

            if ( ! (x = sfstruse(state.disc.sp)) )
                bad("out of space [discipline string]\n", NiL, NiL, 0, 0);
            if ( strcmp(p, x) ) {
                if ( test &
                     (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY | TEST_VERIFY) )
                    return 0;
                report("callout failed", NiL, re, s, len, NiL, flags, test);
                quote(p, -1, test);
                printf(" expected, ");
                quote(x, -1, test);
                printf(" returned\n");
            }
#endif
            break;
        }
        if ( *p++ != '(' )
            bad("improper answer\n", re, s, -1, test);
        if ( *p == '?' ) {
            m = -1;
            p++;
        }
        else
            m = strtol(p, &p, 10);
        if ( *p++ != ',' )
            bad("improper answer\n", re, s, -1, test);
        if ( *p == '?' ) {
            n = -1;
            p++;
        }
        else
            n = strtol(p, &p, 10);
        if ( *p++ != ')' )
            bad("improper answer\n", re, s, -1, test);
        if ( m != match[i].rm_so || n != match[i].rm_eo ) {
            if ( ! (test &
                    (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY | TEST_VERIFY)) ) {
                report("failed: match was", NiL, re, s, len, NiL, flags, test);
                matchprint(match, nmatch, nsub, ans, test);
            }
            return 0;
        }
    }
    for ( ; i < nmatch; i++ ) {
        if ( match[i].rm_so != -1 || match[i].rm_eo != -1 ) {
            if ( ! (test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_VERIFY)) ) {
                if ( (test & TEST_IGNORE_POSITION) && (match[i].rm_so < 0 || match[i].rm_eo < 0) ) {
                    state.ignored++;
                    return 0;
                }
                if ( ! (test & TEST_SUMMARY) ) {
                    report("failed: match was", NiL, re, s, len, NiL, flags, test);
                    matchprint(match, nmatch, nsub, ans, test);
                }
            }
            return 0;
        }
    }
    if ( ! (test & TEST_IGNORE_OVER) && match[nmatch].rm_so != state.NOMATCH.rm_so ) {
        if ( ! (test &
                (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY | TEST_VERIFY)) ) {
            report("failed: overran match array", NiL, re, s, len, NiL, flags, test);
            matchprint(match, nmatch + 1, nsub, NiL, test);
        }
        return 0;
    }
    return 1;
}

static void sigunblock(int s) {
#ifdef SIG_SETMASK
    int op;
    sigset_t mask;

    sigemptyset(&mask);
    if ( s ) {
        sigaddset(&mask, s);
        op = SIG_UNBLOCK;
    }
    else
        op = SIG_SETMASK;
    sigprocmask(op, &mask, NiL);
#else
#ifdef sigmask
    sigsetmask(s ? (sigsetmask(0L) & ~sigmask(s)) : 0L);
#endif
#endif
}

static void gotcha(int sig) {
    int ret;

    signal(sig, gotcha);
    alarm(0);
    state.signals++;
    switch ( sig ) {
        case SIGALRM: ret = REG_EHUNG; break;
        case SIGBUS: ret = REG_EBUS; break;
        default: ret = REG_EFAULT; break;
    }
    sigunblock(sig);
    longjmp(state.gotcha, ret);
}

static char* mygetline(FILE* fp) {
    static char buf[32 * 1024];

    register char* s = buf;
    register char* e = &buf[sizeof(buf)];
    register char* b;

    for ( ;; ) {
        if ( ! (b = fgets(s, e - s, fp)) )
            return 0;
        state.lineno++;
        s += strlen(s);
        if ( s == b || *--s != '\n' || s == b || *(s - 1) != '\\' ) {
            *s = 0;
            break;
        }
        s--;
    }
    return buf;
}

static unsigned long note(unsigned long level, char* msg, unsigned long skip, unsigned long test) {
    if ( ! (test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_SUMMARY)) && ! skip ) {
        printf("NOTE\t");
        if ( msg )
            printf("%s: ", msg);
        printf("skipping lines %d", state.lineno);
    }
    return skip | level;
}

#define TABS(n) &ts[7 - ((n) & 7)]

static char ts[] = "\t\t\t\t\t\t\t";

static unsigned long extract(int* tabs, char* spec, char* re, char* s, char* ans, char* msg, char* accept,
                             regmatch_t* match, int nmatch, int nsub, unsigned long skip, unsigned long level,
                             unsigned long test) {
    if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_OK | TEST_PASS | TEST_SUMMARY) ) {
        state.extracted = 1;
        if ( test & TEST_OK ) {
            state.passed++;
            if ( (test & TEST_VERIFY) &&
                 ! (test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_SUMMARY)) ) {
                if ( msg && strcmp(msg, "EXPECTED") != 0 )
                    printf("NOTE\t%s\n", msg);
                return skip;
            }
            test &= ~(TEST_PASS | TEST_QUERY);
        }
        if ( test & (TEST_QUERY | TEST_VERIFY) ) {
            if ( test & TEST_BASELINE )
                test &= ~(TEST_BASELINE | TEST_PASS);
            else
                test |= TEST_PASS;
            skip |= level;
        }
        if ( ! (test & TEST_OK) ) {
            if ( test & TEST_UNSPECIFIED )
                state.unspecified++;
            else
                state.errors++;
        }
        if ( test & (TEST_PASS | TEST_SUMMARY) )
            return skip;
        test &= ~TEST_DELIMIT;
        printf("%s%s", spec, TABS(*tabs++));
        if ( (test & (TEST_BASELINE | TEST_SAME)) == (TEST_BASELINE | TEST_SAME) )
            printf("SAME");
        else
            quote(re, -1, test);
        printf("%s", TABS(*tabs++));
        quote(s, -1, test);
        printf("%s", TABS(*tabs++));
        if ( ! (test & (TEST_ACTUAL | TEST_BASELINE)) || (! accept && ! match) )
            printf("%s", ans);
        else if ( accept )
            printf("%s", accept);
        else
            matchprint(match, nmatch, nsub, NiL, test);
        if ( msg )
            printf("%s%s", TABS(*tabs++), msg);
        putchar('\n');
    }
    else if ( test & TEST_QUERY )
        skip = note(level, msg, skip, test);
    else if ( test & TEST_VERIFY )
        state.extracted = 1;
    return skip;
}

static int catchfree(regex_t* preg, int flags, int* tabs, char* spec, char* re, char* s, char* ans, char* msg,
                     char* accept, regmatch_t* match, int nmatch, int nsub, unsigned long skip, unsigned long level,
                     unsigned long test) {
    int eret;

    if ( ! (test & TEST_CATCH) ) {
        regfree(preg);
        eret = 0;
    }
    else if ( ! (eret = setjmp(state.gotcha)) ) {
        alarm(HUNG);
        regfree(preg);
        alarm(0);
    }
    else if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY | TEST_VERIFY) )
        extract(tabs, spec, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test);
    else {
        report("failed", "regfree", re, NiL, -1, msg, flags, test);
        error(preg, eret);
    }
    return eret;
}

int main(int argc, char** argv) {
    int flags = 0;
    int cflags;
    int eflags;
    int nmatch;
    int nexec;
    int nstr;
    int cret;
    int eret;
    int nsub;
    int i;
    int j;
    int expected;
    int got;
    int locale;
    int subunitlen;
    int testno;
    unsigned long level;
    unsigned long skip;
    char* p;
    char* line;
    char* spec;
    char* re;
    char* s;
    char* ans;
    char* msg;
    char* fun;
    char* ppat;
    char* subunit;
    char* version;
    char* field[6];
    char* delim[6];
    FILE* fp;
    int tabs[6];
    char unit[64];
    regmatch_t match[100];
    regex_t preg;

    static char pat[32 * 1024];

    int nonosub = REG_NOSUB == 0;
    int nonexec = 0;

    unsigned long test = 0;

    static char* filter[] = {"-", 0};

    state.NOMATCH.rm_so = state.NOMATCH.rm_eo = -2;
    p = unit;
    version = (char*)id + 10;
    while ( p < &unit[sizeof(unit) - 1] && (*p = *version++) && ! isspace(*p) )
        p++;
    *p = 0;
    while ( (p = *++argv) && *p == '-' )
        for ( ;; ) {
            switch ( *++p ) {
                case 0: break;
                case 'c': test |= TEST_CATCH; continue;
                case 'e': test |= TEST_IGNORE_ERROR; continue;
                case 'l': flags |= REG_LAZY; continue;
                case 'h':
                case '?': help(0); return 2;
                case '-': help(p[1] == 'h'); return 2;
                case 'n': nonexec = 1; continue;
                case 'o': test |= TEST_IGNORE_OVER; continue;
                case 'p': test |= TEST_IGNORE_POSITION; continue;
                case 's':
#ifdef REG_DISCIPLINE
                    if ( ! (state.stack = stkalloc(stkstd, 0)) )
                        fprintf(stderr, "%s: out of space [stack]", unit);
                    state.disc.disc.re_resizef = resizef;
                    state.disc.disc.re_resizehandle = (void*)stkstd;
#endif
                    continue;
                case 'x': nonosub = 1; continue;
                case 'v': test |= TEST_VERBOSE; continue;
                case 'A': test |= TEST_ACTUAL; continue;
                case 'B': test |= TEST_BASELINE; continue;
                case 'F': test |= TEST_FAIL; continue;
                case 'P': test |= TEST_PASS; continue;
                case 'S': test |= TEST_SUMMARY; continue;
                default: fprintf(stderr, "%s: %c: invalid option\n", unit, *p); return 2;
            }
            break;
        }
    if ( ! *argv )
        argv = filter;
    locale = 0;
    while ( (state.file = *argv++) ) {
        if ( streq(state.file, "-") || streq(state.file, "/dev/stdin") || streq(state.file, "/dev/fd/0") ) {
            state.file = 0;
            fp = stdin;
        }
        else if ( ! (fp = fopen(state.file, "re")) ) {
            fprintf(stderr, "%s: %s: cannot read\n", unit, state.file);
            return 2;
        }
        testno = state.errors = state.ignored = state.lineno = state.passed = state.signals = state.unspecified =
            state.warnings = 0;
        skip = 0;
        level = 1;
        if ( ! (test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_SUMMARY)) ) {
            printf("TEST\t%s ", unit);
            if ( (s = state.file) ) {
                subunit = p = 0;
                for ( ;; ) {
                    switch ( *s++ ) {
                        case 0: break;
                        case '/': subunit = s; continue;
                        case '.': p = s - 1; continue;
                        default: continue;
                    }
                    break;
                }
                if ( ! subunit )
                    subunit = state.file;
                if ( p < subunit )
                    p = s - 1;
                subunitlen = p - subunit;
                printf("%-.*s ", subunitlen, subunit);
            }
            else
                subunit = 0;
            for ( s = version; *s && (*s != ' ' || *(s + 1) != '$'); s++ )
                putchar(*s);
            if ( test & TEST_CATCH )
                printf(", catch");
            if ( test & TEST_IGNORE_ERROR )
                printf(", ignore error code mismatches");
            if ( test & TEST_IGNORE_POSITION )
                printf(", ignore negative position mismatches");
#ifdef REG_DISCIPLINE
            if ( state.stack )
                printf(", stack");
#endif
            if ( test & TEST_VERBOSE )
                printf(", verbose");
            printf("\n");
#ifdef REG_VERSIONID
            if ( regerror(REG_VERSIONID, NiL, pat, sizeof(pat)) > 0 )
                s = pat;
            else
#endif
#ifdef REG_TEST_VERSION
                s = REG_TEST_VERSION;
#else
            s = "regex";
#endif
            printf("NOTE\t%s\n", s);
            if ( elementsof(unsupported) > 1 ) {
#if ( REG_TEST_DEFAULT & (REG_AUGMENTED | REG_EXTENDED | REG_SHELL) ) || ! defined(REG_EXTENDED)
                i = 0;
#else
                i = REG_EXTENDED != 0;
#endif
                for ( got = 0; i < elementsof(unsupported) - 1; i++ ) {
                    if ( ! got ) {
                        got = 1;
                        printf("NOTE\tunsupported: %s", unsupported[i]);
                    }
                    else
                        printf(",%s", unsupported[i]);
                }
                if ( got )
                    printf("\n");
            }
        }
#ifdef REG_DISCIPLINE
        state.disc.disc.re_version = REG_VERSION;
        state.disc.disc.re_compf = compf;
        state.disc.disc.re_execf = execf;
        if ( ! (state.disc.sp = sfstropen()) )
            bad("out of space [discipline string stream]\n", NiL, NiL, 0, 0);
        preg.re_disc = &state.disc.disc;
#endif
        if ( test & TEST_CATCH ) {
            signal(SIGALRM, gotcha);
            signal(SIGBUS, gotcha);
            signal(SIGSEGV, gotcha);
        }
        while ( (p = mygetline(fp)) ) {
            /* parse: */

            line = p;
            if ( *p == ':' && ! isspace(*(p + 1)) ) {
                while ( *++p && *p != ':' )
                    ;
                if ( ! *p++ ) {
                    if ( test & TEST_BASELINE )
                        printf("%s\n", line);
                    continue;
                }
            }
            while ( isspace(*p) )
                p++;
            if ( *p == 0 || *p == '#' || *p == 'T' ) {
                if ( test & TEST_BASELINE )
                    printf("%s\n", line);
                continue;
            }
            if ( *p == ':' || *p == 'N' ) {
                if ( test & TEST_BASELINE )
                    printf("%s\n", line);
                else if ( ! (test & (TEST_ACTUAL | TEST_FAIL | TEST_PASS | TEST_SUMMARY)) ) {
                    while ( *++p && ! isspace(*p) )
                        ;
                    while ( isspace(*p) )
                        p++;
                    printf("NOTE	%s\n", p);
                }
                continue;
            }
            j = 0;
            i = 0;
            field[i++] = p;
            for ( ;; ) {
                switch ( *p++ ) {
                    case 0:
                        p--;
                        j = 0;
                        goto checkfield;
                    case '\t':
                        *(delim[i] = p - 1) = 0;
                        j = 1;
                    checkfield:
                        s = field[i - 1];
                        if ( streq(s, "NIL") )
                            field[i - 1] = 0;
                        else if ( streq(s, "NULL") )
                            *s = 0;
                        while ( *p == '\t' ) {
                            p++;
                            j++;
                        }
                        tabs[i - 1] = j;
                        if ( ! *p )
                            break;
                        if ( i >= elementsof(field) )
                            bad("too many fields\n", NiL, NiL, 0, 0);
                        field[i++] = p;
                    /*FALLTHROUGH*/
                    default: continue;
                }
                break;
            }
            if ( ! (spec = field[0]) )
                bad("NIL spec\n", NiL, NiL, 0, 0);

            /* interpret: */

            cflags = REG_TEST_DEFAULT;
            eflags = REG_EXEC_DEFAULT;
            test &= TEST_GLOBAL;
            state.extracted = 0;
            nmatch = 20;
            nsub = -1;
            for ( p = spec; *p; p++ ) {
                if ( isdigit(*p) ) {
                    nmatch = strtol(p, &p, 10);
                    if ( nmatch >= elementsof(match) )
                        bad("nmatch must be < 100\n", NiL, NiL, 0, 0);
                    p--;
                    continue;
                }
                switch ( *p ) {
                    case 'A': test |= TEST_ARE; continue;
                    case 'B':
                        //					test |= TEST_BRE;
                        continue;
                    case 'C':
                        if ( ! (test & TEST_QUERY) && ! (skip & level) )
                            bad("locale must be nested\n", NiL, NiL, 0, 0);
                        test &= ~TEST_QUERY;
                        if ( locale )
                            bad("locale nesting not supported\n", NiL, NiL, 0, 0);
                        if ( i != 2 )
                            bad("locale field expected\n", NiL, NiL, 0, 0);
                        if ( ! (skip & level) ) {
#if defined(LC_COLLATE) && defined(LC_CTYPE)
                            s = field[1];
                            if ( ! s || streq(s, "POSIX") )
                                s = "C";
                            if ( ! (ans = setlocale(LC_COLLATE, s)) || streq(ans, "C") || streq(ans, "POSIX") ||
                                 ! (ans = setlocale(LC_CTYPE, s)) || streq(ans, "C") || streq(ans, "POSIX") )
                                skip = note(level, s, skip, test);
                            else {
                                if ( ! (test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_SUMMARY)) )
                                    printf("NOTE	\"%s\" locale\n", s);
                                locale = level;
                            }
#else
                            skip = note(level, skip, test, "locales not supported");
#endif
                        }
                        cflags = NOTEST;
                        continue;
                    case 'E': test |= TEST_ERE; continue;
                    case 'K': test |= TEST_KRE; continue;
                    case 'L': test |= TEST_LRE; continue;
                    case 'S': test |= TEST_SRE; continue;

                    case 'a': cflags |= REG_LEFT | REG_RIGHT; continue;
                    case 'b': eflags |= REG_NOTBOL; continue;
                    case 'c': cflags |= REG_COMMENT; continue;
                    case 'd': cflags |= REG_SHELL_DOT; continue;
                    case 'e': eflags |= REG_NOTEOL; continue;
                    case 'f': cflags |= REG_MULTIPLE; continue;
                    case 'g': cflags |= NOTEST; continue;
                    case 'h': cflags |= REG_MULTIREF; continue;
                    case 'i': cflags |= REG_ICASE; continue;
                    case 'j': cflags |= REG_SPAN; continue;
                    case 'k': cflags |= REG_ESCAPE; continue;
                    case 'l': cflags |= REG_LEFT; continue;
                    case 'm': cflags |= REG_MINIMAL; continue;
                    case 'n': cflags |= REG_NEWLINE; continue;
                    case 'o': cflags |= REG_SHELL_GROUP; continue;
                    case 'p': cflags |= REG_SHELL_PATH; continue;
                    case 'q': cflags |= REG_DELIMITED; continue;
                    case 'r': cflags |= REG_RIGHT; continue;
                    case 's': cflags |= REG_SHELL_ESCAPED; continue;
                    case 't': cflags |= REG_MUSTDELIM; continue;
                    case 'u': test |= TEST_UNSPECIFIED; continue;
                    case 'w': cflags |= REG_NOSUB; continue;
                    case 'x':
                        if ( REG_LENIENT )
                            cflags |= REG_LENIENT;
                        else
                            test |= TEST_LENIENT;
                        continue;
                    case 'y': eflags |= REG_LEFT; continue;
                    case 'z': cflags |= REG_NULL; continue;
                    case '$': test |= TEST_EXPAND; continue;

                    case '/': test |= TEST_SUB; continue;

                    case '=': test |= TEST_DECOMP; continue;

                    case '?':
                        test |= TEST_VERIFY;
                        test &= ~(TEST_AND | TEST_OR);
                        state.verify = state.passed;
                        continue;
                    case '&':
                        test |= TEST_VERIFY | TEST_AND;
                        test &= ~TEST_OR;
                        continue;
                    case '|':
                        test |= TEST_VERIFY | TEST_OR;
                        test &= ~TEST_AND;
                        continue;
                    case ';':
                        test |= TEST_OR;
                        test &= ~TEST_AND;
                        continue;

                    case '{':
                        level <<= 1;
                        if ( skip & (level >> 1) ) {
                            skip |= level;
                            cflags = NOTEST;
                        }
                        else {
                            skip &= ~level;
                            test |= TEST_QUERY;
                        }
                        continue;
                    case '}':
                        if ( level == 1 )
                            bad("invalid {...} nesting\n", NiL, NiL, 0, 0);
                        if ( (skip & level) && ! (skip & (level >> 1)) ) {
                            if ( ! (test & (TEST_BASELINE | TEST_SUMMARY)) ) {
                                if ( test & (TEST_ACTUAL | TEST_FAIL) )
                                    printf("}\n");
                                else if ( ! (test & TEST_PASS) )
                                    printf("-%d\n", state.lineno);
                            }
                        }
#if defined(LC_COLLATE) && defined(LC_CTYPE)
                        else if ( locale & level ) {
                            locale = 0;
                            if ( ! (skip & level) ) {
                                s = "C";
                                setlocale(LC_COLLATE, s);
                                setlocale(LC_CTYPE, s);
                                if ( ! (test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_SUMMARY)) )
                                    printf("NOTE	\"%s\" locale\n", s);
                                else if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_PASS) )
                                    printf("}\n");
                            }
                            else if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL) )
                                printf("}\n");
                        }
#endif
                        level >>= 1;
                        cflags = NOTEST;
                        continue;

                    default: bad("bad spec\n", spec, NiL, 0, test); break;
                }
                break;
            }
            if ( (cflags | eflags) == NOTEST || ((skip & level) && (test & TEST_BASELINE)) ) {
                if ( test & TEST_BASELINE ) {
                    while ( i > 1 )
                        *delim[--i] = '\t';
                    printf("%s\n", line);
                }
                continue;
            }
            if ( test & TEST_OR ) {
                if ( ! (test & TEST_VERIFY) ) {
                    test &= ~TEST_OR;
                    if ( state.passed == state.verify && i > 1 )
                        printf("NOTE\t%s\n", field[1]);
                    continue;
                }
                if ( state.passed > state.verify )
                    continue;
            }
            else if ( test & TEST_AND ) {
                if ( state.passed == state.verify )
                    continue;
                state.passed = state.verify;
            }
            if ( i < ((test & TEST_DECOMP) ? 3 : 4) )
                bad("too few fields\n", NiL, NiL, 0, test);
            while ( i < elementsof(field) )
                field[i++] = 0;
            if ( (re = field[1]) ) {
                if ( streq(re, "SAME") ) {
                    re = ppat;
                    test |= TEST_SAME;
                }
                else {
                    if ( test & TEST_EXPAND )
                        escape(re);
                    strcpy(ppat = pat, re);
                }
            }
            else
                ppat = 0;
            nstr = -1;
            if ( (s = field[2]) && (test & TEST_EXPAND) ) {
                nstr = escape(s);
#if _REG_nexec
                if ( nstr != strlen(s) )
                    nexec = nstr;
#endif
            }
            if ( ! (ans = field[(test & TEST_DECOMP) ? 2 : 3]) )
                bad("NIL answer\n", NiL, NiL, 0, test);
            msg = field[4];
            fflush(stdout);
            if ( test & TEST_SUB )
#if _REG_subcomp
                cflags |= REG_DELIMITED;
#else
                continue;
#endif
#if ! _REG_decomp
            if ( test & TEST_DECOMP )
                continue;
#endif

        compile:

            if ( state.extracted || (skip & level) )
                continue;
#if ! (REG_TEST_DEFAULT & (REG_AUGMENTED | REG_EXTENDED | REG_SHELL))
#ifdef REG_EXTENDED
            if ( REG_EXTENDED != 0 && (test & TEST_BRE) )
#else
            if ( test & TEST_BRE )
#endif
            {
                test &= ~TEST_BRE;
                flags = cflags;
                state.which = "BRE";
            }
            else
#endif
#ifdef REG_EXTENDED
                if ( test & TEST_ERE ) {
                test &= ~TEST_ERE;
                flags = cflags | REG_EXTENDED;
                state.which = "ERE";
            }
            else
#endif
#ifdef REG_AUGMENTED
                if ( test & TEST_ARE ) {
                test &= ~TEST_ARE;
                flags = cflags | REG_AUGMENTED;
                state.which = "ARE";
            }
            else
#endif
#ifdef REG_LITERAL
                if ( test & TEST_LRE ) {
                test &= ~TEST_LRE;
                flags = cflags | REG_LITERAL;
                state.which = "LRE";
            }
            else
#endif
#ifdef REG_SHELL
                if ( test & TEST_SRE ) {
                test &= ~TEST_SRE;
                flags = cflags | REG_SHELL;
                state.which = "SRE";
            }
            else
#ifdef REG_AUGMENTED
                if ( test & TEST_KRE ) {
                test &= ~TEST_KRE;
                flags = cflags | REG_SHELL | REG_AUGMENTED;
                state.which = "KRE";
            }
            else
#endif
#endif
            {
                if ( test & (TEST_BASELINE | TEST_PASS | TEST_VERIFY) )
                    extract(tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test | TEST_OK);
                continue;
            }
            if ( (test & (TEST_QUERY | TEST_VERBOSE | TEST_VERIFY)) == TEST_VERBOSE ) {
                printf("test %-3d %s ", state.lineno, state.which);
                quote(re, -1, test | TEST_DELIMIT);
                printf(" ");
                quote(s, nstr, test | TEST_DELIMIT);
                printf("\n");
            }

        nosub:
            fun = "regcomp";
#if _REG_nexec
            if ( nstr >= 0 && nstr != strlen(s) )
                nexec = nstr;

            else
#endif
                nexec = -1;
            if ( state.extracted || (skip & level) )
                continue;
            if ( ! (test & TEST_QUERY) )
                testno++;
#ifdef REG_DISCIPLINE
            if ( state.stack )
                stkset(stkstd, state.stack, 0);
            flags |= REG_DISCIPLINE;
            state.disc.ordinal = 0;
            sfstrseek(state.disc.sp, 0, SEEK_SET);
#endif
            if ( ! (test & TEST_CATCH) )
                cret = regcomp(&preg, re, flags | REG_LAZY);
            else if ( ! (cret = setjmp(state.gotcha)) ) {
                alarm(HUNG);
                cret = regcomp(&preg, re, flags | REG_LAZY);
                alarm(0);
            }
#if _REG_subcomp
            if ( ! cret && (test & TEST_SUB) ) {
                fun = "regsubcomp";
                p = re + preg.re_npat;
                if ( ! (test & TEST_CATCH) )
                    cret = regsubcomp(&preg, p, NiL, 0, 0);
                else if ( ! (cret = setjmp(state.gotcha)) ) {
                    alarm(HUNG);
                    cret = regsubcomp(&preg, p, NiL, 0, 0);
                    alarm(0);
                }
                if ( ! cret && *(p += preg.re_npat) && ! (preg.re_sub->re_flags & REG_SUB_LAST) ) {
                    if ( catchfree(&preg, flags, tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test) )
                        continue;
                    cret = REG_EFLAGS;
                }
            }
#endif
#if _REG_decomp
            if ( ! cret && (test & TEST_DECOMP) ) {
                char buf[128];

                if ( (j = nmatch) > sizeof(buf) )
                    j = sizeof(buf);
                fun = "regdecomp";
                p = re + preg.re_npat;
                if ( ! (test & TEST_CATCH) )
                    i = regdecomp(&preg, -1, buf, j);
                else if ( ! (cret = setjmp(state.gotcha)) ) {
                    alarm(HUNG);
                    i = regdecomp(&preg, -1, buf, j);
                    alarm(0);
                }
                if ( ! cret ) {
                    catchfree(&preg, flags, tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test);
                    if ( i > j ) {
                        if ( i != (strlen(ans) + 1) ) {
                            report("failed", fun, re, s, nstr, msg, flags, test);
                            printf(" %d byte buffer supplied, %d byte buffer required\n", j, i);
                        }
                    }
                    else if ( strcmp(buf, ans) ) {
                        report("failed", fun, re, s, nstr, msg, flags, test);
                        quote(ans, -1, test | TEST_DELIMIT);
                        printf(" expected, ");
                        quote(buf, -1, test | TEST_DELIMIT);
                        printf(" returned\n");
                    }
                    continue;
                }
            }
#endif
            if ( ! cret ) {
                if ( ! (flags & REG_NOSUB) && nsub < 0 && *ans == '(' ) {
                    for ( p = ans; *p; p++ )
                        if ( *p == '(' )
                            nsub++;
                        else if ( *p == '{' )
                            nsub--;
                    if ( nsub >= 0 ) {
                        if ( test & TEST_IGNORE_OVER ) {
                            if ( nmatch > nsub )
                                nmatch = nsub + 1;
                        }
                        else if ( nsub != preg.re_nsub ) {
                            if ( nsub > preg.re_nsub ) {
                                if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY |
                                             TEST_SUMMARY | TEST_VERIFY) )
                                    skip = extract(tabs, line, re, s, ans, msg, "OK", NiL, 0, 0, skip, level,
                                                   test | TEST_DELIMIT);
                                else {
                                    report("re_nsub incorrect", fun, re, NiL, -1, msg, flags, test);
                                    printf("at least %d expected, %zu returned\n", nsub, preg.re_nsub);
                                    state.errors++;
                                }
                            }
                            else
                                nsub = preg.re_nsub;
                        }
                    }
                }
                if ( ! (test & (TEST_DECOMP | TEST_SUB)) && *ans && *ans != '(' && ! streq(ans, "OK") &&
                     ! streq(ans, "NOMATCH") ) {
                    if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY |
                                 TEST_VERIFY) )
                        skip = extract(tabs, line, re, s, ans, msg, "OK", NiL, 0, 0, skip, level, test | TEST_DELIMIT);
                    else if ( ! (test & TEST_LENIENT) ) {
                        report("failed", fun, re, NiL, -1, msg, flags, test);
                        printf("%s expected, OK returned\n", ans);
                    }
                    catchfree(&preg, flags, tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test);
                    continue;
                }
            }
            else {
                if ( test & TEST_LENIENT )
                    /* we'll let it go this time */;
                else if ( ! *ans || ans[0] == '(' || (cret == REG_BADPAT && streq(ans, "NOMATCH")) ) {
                    got = 0;
                    for ( i = 1; i < elementsof(codes); i++ )
                        if ( cret == codes[i].code )
                            got = i;
                    if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY |
                                 TEST_VERIFY) )
                        skip = extract(tabs, line, re, s, ans, msg, codes[got].name, NiL, 0, 0, skip, level,
                                       test | TEST_DELIMIT);
                    else {
                        report("failed", fun, re, NiL, -1, msg, flags, test);
                        printf("%s returned: ", codes[got].name);
                        error(&preg, cret);
                    }
                }
                else {
                    expected = got = 0;
                    for ( i = 1; i < elementsof(codes); i++ ) {
                        if ( streq(ans, codes[i].name) )
                            expected = i;
                        if ( cret == codes[i].code )
                            got = i;
                    }
                    if ( ! expected ) {
                        if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY |
                                     TEST_VERIFY) )
                            skip = extract(tabs, line, re, s, ans, msg, codes[got].name, NiL, 0, 0, skip, level,
                                           test | TEST_DELIMIT);
                        else {
                            report("failed: invalid error code", NiL, re, NiL, -1, msg, flags, test);
                            printf("%s expected, %s returned\n", ans, codes[got].name);
                        }
                    }
                    else if ( cret != codes[expected].code && cret != REG_BADPAT ) {
                        if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY |
                                     TEST_VERIFY) )
                            skip = extract(tabs, line, re, s, ans, msg, codes[got].name, NiL, 0, 0, skip, level,
                                           test | TEST_DELIMIT);
                        else if ( test & TEST_IGNORE_ERROR )
                            state.ignored++;
                        else {
                            report("should fail and did", fun, re, NiL, -1, msg, flags, test);
                            printf("%s expected, %s returned: ", ans, codes[got].name);
                            state.errors--;
                            state.warnings++;
                            error(&preg, cret);
                        }
                    }
                }
                goto compile;
            }

#if _REG_nexec
        execute:
            if ( nexec >= 0 )
                fun = "regnexec";
            else
#endif
                fun = "regexec";

            for ( i = 0; i < elementsof(match); i++ )
                match[i] = state.NOMATCH;

#if _REG_nexec
            if ( nexec >= 0 ) {
                eret = regnexec(&preg, s, nexec, nmatch, match, eflags);
                s[nexec] = 0;
            }
            else
#endif
            {
                if ( ! (test & TEST_CATCH) )
                    eret = regexec(&preg, s, nmatch, match, eflags);
                else if ( ! (eret = setjmp(state.gotcha)) ) {
                    alarm(HUNG);
                    eret = regexec(&preg, s, nmatch, match, eflags);
                    alarm(0);
                }
            }
#if _REG_subcomp
            if ( (test & TEST_SUB) && ! eret ) {
                fun = "regsubexec";
                if ( ! (test & TEST_CATCH) )
                    eret = regsubexec(&preg, s, nmatch, match);
                else if ( ! (eret = setjmp(state.gotcha)) ) {
                    alarm(HUNG);
                    eret = regsubexec(&preg, s, nmatch, match);
                    alarm(0);
                }
            }
#endif
            if ( flags & REG_NOSUB ) {
                if ( eret ) {
                    if ( eret != REG_NOMATCH || ! streq(ans, "NOMATCH") ) {
                        if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY |
                                     TEST_VERIFY) )
                            skip = extract(tabs, line, re, s, ans, msg, "NOMATCH", NiL, 0, 0, skip, level,
                                           test | TEST_DELIMIT);
                        else {
                            report("REG_NOSUB failed", fun, re, s, nstr, msg, flags, test);
                            error(&preg, eret);
                        }
                    }
                }
                else if ( streq(ans, "NOMATCH") ) {
                    if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY |
                                 TEST_VERIFY) )
                        skip = extract(tabs, line, re, s, ans, msg, NiL, match, nmatch, nsub, skip, level,
                                       test | TEST_DELIMIT);
                    else {
                        report("should fail and didn't", fun, re, s, nstr, msg, flags, test);
                        error(&preg, eret);
                    }
                }
            }
            else if ( eret ) {
                if ( eret != REG_NOMATCH || ! streq(ans, "NOMATCH") ) {
                    if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY |
                                 TEST_VERIFY) )
                        skip = extract(tabs, line, re, s, ans, msg, "NOMATCH", NiL, 0, nsub, skip, level,
                                       test | TEST_DELIMIT);
                    else {
                        report("failed", fun, re, s, nstr, msg, flags, test);
                        if ( eret != REG_NOMATCH )
                            error(&preg, eret);
                        else if ( *ans )
                            printf("expected: %s\n", ans);
                        else
                            printf("\n");
                    }
                }
            }
            else if ( streq(ans, "NOMATCH") ) {
                if ( test &
                     (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY | TEST_VERIFY) )
                    skip = extract(tabs, line, re, s, ans, msg, NiL, match, nmatch, nsub, skip, level,
                                   test | TEST_DELIMIT);
                else {
                    report("should fail and didn't", fun, re, s, nstr, msg, flags, test);
                    matchprint(match, nmatch, nsub, NiL, test);
                }
            }
#if _REG_subcomp
            else if ( test & TEST_SUB ) {
                p = preg.re_sub->re_buf;
                if ( strcmp(p, ans) ) {
                    report("failed", fun, re, s, nstr, msg, flags, test);
                    quote(ans, -1, test | TEST_DELIMIT);
                    printf(" expected, ");
                    quote(p, -1, test | TEST_DELIMIT);
                    printf(" returned\n");
                }
            }
#endif
            else if ( ! *ans ) {
                if ( match[0].rm_so != state.NOMATCH.rm_so ) {
                    if ( test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY |
                                 TEST_VERIFY) )
                        skip = extract(tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test);
                    else {
                        report("failed: no match but match array assigned", NiL, re, s, nstr, msg, flags, test);
                        matchprint(match, nmatch, nsub, NiL, test);
                    }
                }
            }
            else if ( matchcheck(match, nmatch, nsub, ans, re, s, nstr, flags, test) ) {
#if _REG_nexec
                if ( nexec < 0 && ! nonexec ) {
                    nexec = nstr >= 0 ? nstr : strlen(s);
                    s[nexec] = '\n';
                    testno++;
                    goto execute;
                }
#endif
                if ( ! (test & (TEST_DECOMP | TEST_SUB | TEST_VERIFY)) && ! nonosub ) {
                    if ( catchfree(&preg, flags, tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test) )
                        continue;
                    flags |= REG_NOSUB;
                    goto nosub;
                }
                if ( test & (TEST_BASELINE | TEST_PASS | TEST_VERIFY) )
                    skip = extract(tabs, line, re, s, ans, msg, NiL, match, nmatch, nsub, skip, level, test | TEST_OK);
            }
            else if ( test &
                      (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS | TEST_QUERY | TEST_SUMMARY | TEST_VERIFY) )
                skip = extract(tabs, line, re, s, ans, msg, NiL, match, nmatch, nsub, skip, level, test | TEST_DELIMIT);
            if ( catchfree(&preg, flags, tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test) )
                continue;
            goto compile;
        }
        if ( test & TEST_SUMMARY )
            printf("tests=%-4d errors=%-4d warnings=%-2d ignored=%-2d unspecified=%-2d signals=%d\n", testno,
                   state.errors, state.warnings, state.ignored, state.unspecified, state.signals);
        else if ( ! (test & (TEST_ACTUAL | TEST_BASELINE | TEST_FAIL | TEST_PASS)) ) {
            printf("TEST\t%s", unit);
            if ( subunit )
                printf(" %-.*s", subunitlen, subunit);
            printf(", %d test%s", testno, testno == 1 ? "" : "s");
            if ( state.ignored )
                printf(", %d ignored mismatche%s", state.ignored, state.ignored == 1 ? "" : "s");
            if ( state.warnings )
                printf(", %d warning%s", state.warnings, state.warnings == 1 ? "" : "s");
            if ( state.unspecified )
                printf(", %d unspecified difference%s", state.unspecified, state.unspecified == 1 ? "" : "s");
            if ( state.signals )
                printf(", %d signal%s", state.signals, state.signals == 1 ? "" : "s");
            printf(", %d error%s\n", state.errors, state.errors == 1 ? "" : "s");
        }
        if ( fp != stdin )
            fclose(fp);
    }
    return 0;
}
