root/src/pkg/regexp/testdata/testregex.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. compf
  2. execf
  3. resizef
  4. help
  5. quote
  6. report
  7. error
  8. bad
  9. escape
  10. matchoffprint
  11. matchprint
  12. matchcheck
  13. sigunblock
  14. gotcha
  15. getline
  16. note
  17. extract
  18. catchfree
  19. expand
  20. main

#pragma prototyped noticed

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

static const char id[] = "\n@(#)$Id: testregex (AT&T Research) 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    gsf@research.att.com        (ksh strmatch, regex extensions)\n");
T("  David Korn      dgk@research.att.com        (ksh glob matcher)\n");
T("  Doug McIlroy    mcilroy@dartmouth.edu       (ast regex/testre in C++)\n");
T("  Tom Lord        lord@regexps.com            (rx tests)\n");
T("  Henry Spencer   henry@zoo.toronto.edu       (original public regex)\n");
T("  Andrew Hume     andrew@research.att.com     (gre tests)\n");
T("  John Maddock    John_Maddock@compuserve.com (regex++ tests)\n");
T("  Philip Hazel    ph10@cam.ac.uk              (pcre tests)\n");
T("  Ville Laurikari vl@iki.fi                   (libtre tests)\n");
H("</PRE>\n");
H("</BODY>\n");
H("</HTML>\n");
}

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

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

#define HUNG            2
#define NOTEST          (~0)

#ifndef REG_TEST_DEFAULT
#define REG_TEST_DEFAULT        0
#endif

#ifndef REG_EXEC_DEFAULT
#define REG_EXEC_DEFAULT        0
#endif

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

#ifndef REG_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*
getline(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;
        int             nexec;
        int             nstr;
        int             cret;
        int             eret;
        int             nsub;
        int             i;
        int             j;
        int             expected;
        int             got;
        int             locale;
        int             subunitlen;
        int             testno;
        unsigned long   level;
        unsigned long   skip;
        char*           p;
        char*           line;
        char*           spec;
        char*           re;
        char*           s;
        char*           ans;
        char*           msg;
        char*           fun;
        char*           ppat;
        char*           subunit;
        char*           version;
        char*           field[6];
        char*           delim[6];
        FILE*           fp;
        int             tabs[6];
        char            unit[64];
        regmatch_t      match[100];
        regex_t         preg;

        static char     pat[32 * 1024];
        static char     patbuf[32 * 1024];
        static char     strbuf[32 * 1024];

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

        unsigned long   test = 0;

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

        state.NOMATCH.rm_so = state.NOMATCH.rm_eo = -2;
        p = unit;
        version = (char*)id + 10;
        while (p < &unit[sizeof(unit)-1] && (*p = *version++) && !isspace(*p))
                p++;
        *p = 0;
        while ((p = *++argv) && *p == '-')
                for (;;)
                {
                        switch (*++p)
                        {
                        case 0:
                                break;
                        case 'c':
                                test |= TEST_CATCH;
                                continue;
                        case 'e':
                                test |= TEST_IGNORE_ERROR;
                                continue;
                        case 'h':
                        case '?':
                                help(0);
                                return 2;
                        case '-':
                                help(p[1] == 'h');
                                return 2;
                        case 'n':
                                nonexec = 1;
                                continue;
                        case 'o':
                                test |= TEST_IGNORE_OVER;
                                continue;
                        case 'p':
                                test |= TEST_IGNORE_POSITION;
                                continue;
                        case 's':
#ifdef REG_DISCIPLINE
                                if (!(state.stack = stkalloc(stkstd, 0)))
                                        fprintf(stderr, "%s: out of space [stack]", unit);
                                state.disc.disc.re_resizef = resizef;
                                state.disc.disc.re_resizehandle = (void*)stkstd;
#endif
                                continue;
                        case 'x':
                                nonosub = 1;
                                continue;
                        case 'v':
                                test |= TEST_VERBOSE;
                                continue;
                        case 'A':
                                test |= TEST_ACTUAL;
                                continue;
                        case 'B':
                                test |= TEST_BASELINE;
                                continue;
                        case 'F':
                                test |= TEST_FAIL;
                                continue;
                        case 'P':
                                test |= TEST_PASS;
                                continue;
                        case 'S':
                                test |= TEST_SUMMARY;
                                continue;
                        default:
                                fprintf(stderr, "%s: %c: invalid option\n", unit, *p);
                                return 2;
                        }
                        break;
                }
        if (!*argv)
                argv = filter;
        locale = 0;
        while (state.file = *argv++)
        {
                if (streq(state.file, "-") || streq(state.file, "/dev/stdin") || streq(state.file, "/dev/fd/0"))
                {
                        state.file = 0;
                        fp = stdin;
                }
                else if (!(fp = fopen(state.file, "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 = getline(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;

                        else
#endif
                                nexec = -1;
                        if (state.extracted || (skip & level))
                                continue;
                        if (!(test & TEST_QUERY))
                                testno++;
#ifdef REG_DISCIPLINE
                        if (state.stack)
                                stkset(stkstd, state.stack, 0);
                        flags |= REG_DISCIPLINE;
                        state.disc.ordinal = 0;
                        sfstrseek(state.disc.sp, 0, SEEK_SET);
#endif
                        if (!(test & TEST_CATCH))
                                cret = regcomp(&preg, re, flags);
                        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;
}

/* [<][>][^][v][top][bottom][index][help] */