root/usr/src/test/libc-tests/tests/regex/testregex.c
/*
 * 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 <glenn.s.fowler@gmail.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) 2010-06-10 $\0\n";

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

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

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

#ifndef RE_DUP_MAX
#define RE_DUP_MAX      32767
#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             0x00000100
#define TEST_LENIENT            0x00000200

#define TEST_QUERY              0x00000400
#define TEST_SUB                0x00000800
#define TEST_UNSPECIFIED        0x00001000
#define TEST_VERIFY             0x00002000
#define TEST_AND                0x00004000
#define TEST_OR                 0x00008000

#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*)((char*)0 + ++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}(%lu:%d)", xlen, xstr, (char*)data - (char*)0, 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 [BEASKLP] then the\n");
T("  specification is a global control line. One or more of [BEASKLP] 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("    v        REG_CLASS_ESCAPE        \\ special inside [...]\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. RE_DUP_MAX inside {...} expands to the\n");
T("    value from <limits.h>.\n");
T("\n");
T("  Field 3: the string to match. X...{RE_DUP_MAX} expands to RE_DUP_MAX\n");
T("    copies of X.\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. RE_DUP_MAX[-+]N expands to the <limits.h> value -+N.\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    glenn.s.fowler@gmail.com        (ksh strmatch, regex extensions)\n");
T("  David Korn      dgkorn@gmail.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_CLASS_ESCAPE
        "CLASS_ESCAPE",
#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_CLASS_ESCAPE
#define REG_CLASS_ESCAPE        NOTEST
#endif
#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 if (*p == 'R' && !memcmp(p, "RE_DUP_MAX", 10))
                {
                        m = RE_DUP_MAX;
                        p += 10;
                        if (*p == '+' || *p == '-')
                                m += strtol(p, &p, 10);
                }
                else
                        m = strtol(p, &p, 10);
                if (*p++ != ',')
                        bad("improper answer\n", re, s, -1, test);
                if (*p == '?')
                {
                        n = -1;
                        p++;
                }
                else if (*p == 'R' && !memcmp(p, "RE_DUP_MAX", 10))
                {
                        n = RE_DUP_MAX;
                        p += 10;
                        if (*p == '+' || *p == '-')
                                n += strtol(p, &p, 10);
                }
                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*
get_line(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"))
                                        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;
}

static char*
expand(char* os, char* ot)
{
        char*   s = os;
        char*   t;
        int     n = 0;
        int     r;
        long    m;

        for (;;)
        {
                switch (*s++)
                {
                case 0:
                        break;
                case '{':
                        n++;
                        continue;
                case '}':
                        n--;
                        continue;
                case 'R':
                        if (n == 1 && !memcmp(s, "E_DUP_MAX", 9))
                        {
                                s--;
                                for (t = ot; os < s; *t++ = *os++);
                                r = ((t - ot) >= 5 && t[-1] == '{' && t[-2] == '.' && t[-3] == '.' && t[-4] == '.') ? t[-5] : 0;
                                os = ot;
                                m = RE_DUP_MAX;
                                if (*(s += 10) == '+' || *s == '-')
                                        m += strtol(s, &s, 10);
                                if (r)
                                {
                                        t -= 5;
                                        while (m-- > 0)
                                                *t++ = r;
                                        while (*s && *s++ != '}');
                                }
                                else
                                        t += snprintf(t, 32, "%ld", m);
                                while (*t = *s++)
                                        t++;
                                break;
                        }
                        continue;
                default:
                        continue;
                }
                break;
        }
        return os;
}

int
main(int argc, char** argv)
{
        int             flags;
        int             cflags;
        int             eflags;
        int             nmatch;
#if _REG_nexec
        int             nexec;
#endif
        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];
        static char     patbuf[32 * 1024];
        static char     strbuf[32 * 1024];

        int             nonosub = REG_NOSUB == 0;
#if _REG_nexec
        int             nonexec = 0;
#endif

        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 'h':
                        case '?':
                                help(0);
                                return 2;
                        case '-':
                                help(p[1] == 'h');
                                return 2;
                        case 'n':
#if _REG_nexec
                                nonexec = 1;
#endif
                                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, "r")))
                {
                        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 = get_line(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, "POSIX"))
                                                        ans = "C";
                                                if (!ans || !streq(ans, s) && streq(s, "C"))
                                                        ans = 0;
                                                else if ((ans = setlocale(LC_CTYPE, s)) && streq(ans, "POSIX"))
                                                        ans = "C";
                                                if (!ans || !streq(ans, s) && streq(s, "C"))
                                                        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 'v':
                                        cflags |= REG_CLASS_ESCAPE;
                                        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;
                                }
                                else 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);
                                        re = expand(re, patbuf);
                                        strcpy(ppat = pat, re);
                                }
                        }
                        else
                                ppat = 0;
                        nstr = -1;
                        if (s = field[2])
                        {
                                s = expand(s, strbuf);
                                if (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;
#endif

                        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);
                        else if (!(cret = setjmp(state.gotcha)))
                        {
                                alarm(HUNG);
                                cret = regcomp(&preg, re, flags);
                                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, %d 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;
}