diff options
Diffstat (limited to 'jimregexp.c')
-rw-r--r-- | jimregexp.c | 1375 |
1 files changed, 1375 insertions, 0 deletions
diff --git a/jimregexp.c b/jimregexp.c new file mode 100644 index 0000000..90c8dd7 --- /dev/null +++ b/jimregexp.c @@ -0,0 +1,1375 @@ +/* + * regcomp and regexec -- regsub and regerror are elsewhere + * + * Copyright (c) 1986 by University of Toronto. + * Written by Henry Spencer. Not derived from licensed software. + * + * Permission is granted to anyone to use this software for any + * purpose on any computer system, and to redistribute it freely, + * subject to the following restrictions: + * + * 1. The author is not responsible for the consequences of use of + * this software, no matter how awful, even if they arise + * from defects in it. + * + * 2. The origin of this software must not be misrepresented, either + * by explicit claim or by omission. + * + * 3. Altered versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + *** THIS IS AN ALTERED VERSION. It was altered by John Gilmore, + *** hoptoad!gnu, on 27 Dec 1986, to add \n as an alternative to | + *** to assist in implementing egrep. + *** THIS IS AN ALTERED VERSION. It was altered by John Gilmore, + *** hoptoad!gnu, on 27 Dec 1986, to add \< and \> for word-matching + *** as in BSD grep and ex. + *** THIS IS AN ALTERED VERSION. It was altered by John Gilmore, + *** hoptoad!gnu, on 28 Dec 1986, to optimize characters quoted with \. + *** THIS IS AN ALTERED VERSION. It was altered by James A. Woods, + *** ames!jaw, on 19 June 1987, to quash a regcomp() redundancy. + *** THIS IS AN ALTERED VERSION. It was altered by Christopher Seiwald + *** seiwald@vix.com, on 28 August 1993, for use in jam. Regmagic.h + *** was moved into regexp.h, and the include of regexp.h now uses "'s + *** to avoid conflicting with the system regexp.h. Const, bless its + *** soul, was removed so it can compile everywhere. The declaration + *** of strchr() was in conflict on AIX, so it was removed (as it is + *** happily defined in string.h). + *** THIS IS AN ALTERED VERSION. It was altered by Christopher Seiwald + *** seiwald@perforce.com, on 20 January 2000, to use function prototypes. + *** THIS IS AN ALTERED VERSION. It was altered by Christopher Seiwald + *** seiwald@perforce.com, on 05 November 2002, to const string literals. + *** THIS IS AN ALTERED VERSION. It was altered by Steve Bennett <steveb@workware.net.au> + *** on 16 October 2010, to remove static state and add better Tcl ARE compatibility. + * + * Beware that some of this code is subtly aware of the way operator + * precedence is structured in regular expressions. Serious changes in + * regular-expression syntax might require a total rethink. + */ +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "jim.h" +#include "jimregexp.h" + +#if !defined(HAVE_REGCOMP) || defined(JIM_REGEXP) + +/* + * Structure for regexp "program". This is essentially a linear encoding + * of a nondeterministic finite-state machine (aka syntax charts or + * "railroad normal form" in parsing technology). Each node is an opcode + * plus a "next" pointer, possibly plus an operand. "Next" pointers of + * all nodes except BRANCH implement concatenation; a "next" pointer with + * a BRANCH on both ends of it is connecting two alternatives. (Here we + * have one of the subtle syntax dependencies: an individual BRANCH (as + * opposed to a collection of them) is never concatenated with anything + * because of operator precedence.) The operand of some types of node is + * a literal string; for others, it is a node leading into a sub-FSM. In + * particular, the operand of a BRANCH node is the first node of the branch. + * (NB this is *not* a tree structure: the tail of the branch connects + * to the thing following the set of BRANCHes.) The opcodes are: + */ + +/* This *MUST* be less than (255-20)/2=117 */ +#define REG_MAX_PAREN 100 + +/* definition number opnd? meaning */ +#define END 0 /* no End of program. */ +#define BOL 1 /* no Match "" at beginning of line. */ +#define EOL 2 /* no Match "" at end of line. */ +#define ANY 3 /* no Match any one character. */ +#define ANYOF 4 /* str Match any character in this string. */ +#define ANYBUT 5 /* str Match any character not in this string. */ +#define BRANCH 6 /* node Match this alternative, or the next... */ +#define BACK 7 /* no Match "", "next" ptr points backward. */ +#define EXACTLY 8 /* str Match this string. */ +#define NOTHING 9 /* no Match empty string. */ +#define STAR 10 /* node Match this (simple) thing 0 or more times. */ +#define PLUS 11 /* node Match this (simple) thing 1 or more times. */ +#define WORDA 12 /* no Match "" at wordchar, where prev is nonword */ +#define WORDZ 13 /* no Match "" at nonwordchar, where prev is word */ +#define OPEN 20 /* no Mark this point in input as start of #n. */ + /* OPEN+1 is number 1, etc. */ +#define CLOSE (OPEN+REG_MAX_PAREN) /* no Analogous to OPEN. */ +#define CLOSE_END (CLOSE+REG_MAX_PAREN) + +/* + * The first byte of the regexp internal "program" is actually this magic + * number; the start node begins in the second byte. + */ +#define REG_MAGIC 0234 + +/* + * Opcode notes: + * + * BRANCH The set of branches constituting a single choice are hooked + * together with their "next" pointers, since precedence prevents + * anything being concatenated to any individual branch. The + * "next" pointer of the last BRANCH in a choice points to the + * thing following the whole choice. This is also where the + * final "next" pointer of each individual branch points; each + * branch starts with the operand node of a BRANCH node. + * + * BACK Normal "next" pointers all implicitly point forward; BACK + * exists to make loop structures possible. + * + * STAR,PLUS '?', and complex '*' and '+', are implemented as circular + * BRANCH structures using BACK. Simple cases (one character + * per match) are implemented with STAR and PLUS for speed + * and to minimize recursive plunges. + * + * OPEN,CLOSE ...are numbered at compile time. + */ + +/* + * A node is one char of opcode followed by two chars of "next" pointer. + * "Next" pointers are stored as two 8-bit pieces, high order first. The + * value is a positive offset from the opcode of the node containing it. + * An operand, if any, simply follows the node. (Note that much of the + * code generation knows about this implicit relationship.) + * + * Using two bytes for the "next" pointer is vast overkill for most things, + * but allows patterns to get big without disasters. + */ +#define OP(p) ((unsigned char)*(p)) +#define NEXT(p) (((*((p)+1)&0377)<<8) + (*((p)+2)&0377)) +#define OPERAND(p) ((p) + 3) + +/* + * See regmagic.h for one further detail of program structure. + */ + + +/* + * Utility definitions. + */ +#ifndef CHARBITS +#define UCHARAT(p) ((int)*(unsigned char *)(p)) +#else +#define UCHARAT(p) ((int)*(p)&CHARBITS) +#endif + +#define FAIL(R,M) { (R)->err = (M); return (M); } +#define ISMULT(c) ((c) == '*' || (c) == '+' || (c) == '?') + +/* + * Flags to be passed up and down. + */ +#define HASWIDTH 01 /* Known never to match null string. */ +#define SIMPLE 02 /* Simple enough to be STAR/PLUS operand. */ +#define SPSTART 04 /* Starts with * or +. */ +#define WORST 0 /* Worst case. */ + +/* + * Forward declarations for regcomp()'s friends. + */ +static char *reg(regex_t *preg, int paren /* Parenthesized? */, int *flagp ); +static char *regpiece(regex_t *preg, int *flagp ); +static char *regbranch(regex_t *preg, int *flagp ); +static char *regatom(regex_t *preg, int *flagp ); +static char *regnode(regex_t *preg, int op ); +static const char *regnext(regex_t *preg, const char *p ); +static void regc(regex_t *preg, int b ); +static void reginsert(regex_t *preg, char op, char *opnd ); +static void regtail(regex_t *preg, char *p, const char *val ); +static void regoptail(regex_t *preg, char *p, const char *val ); + +static const char *str_find(const char *string, char c, int nocase); + +/*#define DEBUG*/ +#ifdef DEBUG +int regnarrate = 0; +static void regdump(regex_t *preg); +static const char *regprop( const char *op ); +#endif + + +static char regdummy; + +/* + - regcomp - compile a regular expression into internal code + * + * We can't allocate space until we know how big the compiled form will be, + * but we can't compile it (and thus know how big it is) until we've got a + * place to put the code. So we cheat: we compile it twice, once with code + * generation turned off and size counting turned on, and once "for real". + * This also means that we don't allocate space until we are sure that the + * thing really will compile successfully, and we never have to move the + * code and thus invalidate pointers into it. (Note that it has to be in + * one piece because free() must be able to free it all.) + * + * Beware that the optimization-preparation code in here knows about some + * of the structure of the compiled regexp. + */ +int regcomp(regex_t *preg, const char *exp, int cflags) +{ + const char *scan; + const char *longest; + unsigned len; + int flags; + + memset(preg, 0, sizeof(*preg)); + + if (exp == NULL) + FAIL(preg, REG_ERR_NULL_ARGUMENT); + + /* First pass: determine size, legality. */ +#ifdef notdef + if (exp[0] == '.' && exp[1] == '*') exp += 2; /* aid grep */ +#endif + preg->cflags = cflags; + preg->regparse = (char *)exp; + preg->re_nsub = 1; + preg->regsize = 0L; + preg->regcode = ®dummy; + regc(preg, REG_MAGIC); + if (reg(preg, 0, &flags) == NULL) + return preg->err; + + /* Small enough for pointer-storage convention? */ + if (preg->regsize >= 32767L || preg->re_nsub >= REG_MAX_PAREN) /* Probably could be 65535L. */ + FAIL(preg,REG_ERR_TOO_BIG); + + /* Allocate space. */ + preg->program = malloc(preg->regsize); + if (preg->program == NULL) + FAIL(preg, REG_ERR_NOMEM); + + /* Second pass: emit code. */ + preg->regparse = (char *)exp; + preg->re_nsub = 1; + preg->regcode = preg->program; + regc(preg, REG_MAGIC); + if (reg(preg, 0, &flags) == NULL) + return preg->err; + + /* Dig out information for optimizations. */ + preg->regstart = '\0'; /* Worst-case defaults. */ + preg->reganch = 0; + preg->regmust = NULL; + preg->regmlen = 0; + scan = preg->program+1; /* First BRANCH. */ + if (OP(regnext(preg, scan)) == END) { /* Only one top-level choice. */ + scan = OPERAND(scan); + + /* Starting-point info. */ + if (OP(scan) == EXACTLY) + preg->regstart = *OPERAND(scan); + else if (OP(scan) == BOL) + preg->reganch++; + + /* + * If there's something expensive in the r.e., find the + * longest literal string that must appear and make it the + * regmust. Resolve ties in favor of later strings, since + * the regstart check works with the beginning of the r.e. + * and avoiding duplication strengthens checking. Not a + * strong reason, but sufficient in the absence of others. + */ + if (flags&SPSTART) { + longest = NULL; + len = 0; + for (; scan != NULL; scan = regnext(preg, scan)) + if (OP(scan) == EXACTLY && strlen(OPERAND(scan)) >= len) { + longest = OPERAND(scan); + len = strlen(OPERAND(scan)); + } + preg->regmust = longest; + preg->regmlen = len; + } + } + +#ifdef DEBUG + /*regdump(preg);*/ +#endif + + return 0; +} + +/* + - reg - regular expression, i.e. main body or parenthesized thing + * + * Caller must absorb opening parenthesis. + * + * Combining parenthesis handling with the base level of regular expression + * is a trifle forced, but the need to tie the tails of the branches to what + * follows makes it hard to avoid. + */ +static char *reg(regex_t *preg, int paren /* Parenthesized? */, int *flagp ) +{ + char *ret; + char *br; + const char *ender; + int parno = 0; + int flags; + + *flagp = HASWIDTH; /* Tentatively. */ + + /* Make an OPEN node, if parenthesized. */ + if (paren) { + parno = preg->re_nsub++; + ret = regnode(preg, OPEN+parno); + } else + ret = NULL; + + /* Pick up the branches, linking them together. */ + br = regbranch(preg, &flags); + if (br == NULL) + return(NULL); + if (ret != NULL) + regtail(preg, ret, br); /* OPEN -> first. */ + else + ret = br; + if (!(flags&HASWIDTH)) + *flagp &= ~HASWIDTH; + *flagp |= flags&SPSTART; + while (*preg->regparse == '|' || *preg->regparse == '\n') { + preg->regparse++; + br = regbranch(preg, &flags); + if (br == NULL) + return(NULL); + regtail(preg, ret, br); /* BRANCH -> BRANCH. */ + if (!(flags&HASWIDTH)) + *flagp &= ~HASWIDTH; + *flagp |= flags&SPSTART; + } + + /* Make a closing node, and hook it on the end. */ + ender = regnode(preg, (paren) ? CLOSE+parno : END); + regtail(preg, ret, ender); + + /* Hook the tails of the branches to the closing node. */ + for (br = ret; br != NULL; br = (char *)regnext(preg, br)) + regoptail(preg, br, ender); + + /* Check for proper termination. */ + if (paren && *preg->regparse++ != ')') { + preg->err = REG_ERR_UNMATCHED_PAREN; + return NULL; + } else if (!paren && *preg->regparse != '\0') { + if (*preg->regparse == ')') { + preg->err = REG_ERR_UNMATCHED_PAREN; + return NULL; + } else { + preg->err = REG_ERR_JUNK_ON_END; + return NULL; + } + } + + return(ret); +} + +/* + - regbranch - one alternative of an | operator + * + * Implements the concatenation operator. + */ +static char *regbranch(regex_t *preg, int *flagp ) +{ + char *ret; + char *chain; + char *latest; + int flags; + + *flagp = WORST; /* Tentatively. */ + + ret = regnode(preg, BRANCH); + chain = NULL; + while (*preg->regparse != '\0' && *preg->regparse != ')' && + *preg->regparse != '\n' && *preg->regparse != '|') { + latest = regpiece(preg, &flags); + if (latest == NULL) + return(NULL); + *flagp |= flags&HASWIDTH; + if (chain == NULL) /* First piece. */ + *flagp |= flags&SPSTART; + else + regtail(preg, chain, latest); + chain = latest; + } + if (chain == NULL) /* Loop ran zero times. */ + (void) regnode(preg, NOTHING); + + return(ret); +} + +/* + - regpiece - something followed by possible [*+?] + * + * Note that the branching code sequences used for ? and the general cases + * of * and + are somewhat optimized: they use the same NOTHING node as + * both the endmarker for their branch list and the body of the last branch. + * It might seem that this node could be dispensed with entirely, but the + * endmarker role is not redundant. + */ +static char *regpiece(regex_t *preg, int *flagp ) +{ + char *ret; + char op; + char *next; + int flags; + + ret = regatom(preg, &flags); + if (ret == NULL) + return(NULL); + + op = *preg->regparse; + if (!ISMULT(op)) { + *flagp = flags; + return(ret); + } + + if (!(flags&HASWIDTH) && op != '?') { + preg->err = REG_ERR_OPERAND_COULD_BE_EMPTY; + return NULL; + } + *flagp = (op != '+') ? (WORST|SPSTART) : (WORST|HASWIDTH); + + if (op == '*' && (flags&SIMPLE)) + reginsert(preg, STAR, ret); + else if (op == '*') { + /* Emit x* as (x&|), where & means "self". */ + reginsert(preg, BRANCH, ret); /* Either x */ + regoptail(preg, ret, regnode(preg, BACK)); /* and loop */ + regoptail(preg, ret, ret); /* back */ + regtail(preg, ret, regnode(preg, BRANCH)); /* or */ + regtail(preg, ret, regnode(preg, NOTHING)); /* null. */ + } else if (op == '+' && (flags&SIMPLE)) + reginsert(preg, PLUS, ret); + else if (op == '+') { + /* Emit x+ as x(&|), where & means "self". */ + next = regnode(preg, BRANCH); /* Either */ + regtail(preg, ret, next); + regtail(preg, regnode(preg, BACK), ret); /* loop back */ + regtail(preg, next, regnode(preg, BRANCH)); /* or */ + regtail(preg, ret, regnode(preg, NOTHING)); /* null. */ + } else if (op == '?') { + /* Emit x? as (x|) */ + reginsert(preg, BRANCH, ret); /* Either x */ + regtail(preg, ret, regnode(preg, BRANCH)); /* or */ + next = regnode(preg, NOTHING); /* null. */ + regtail(preg, ret, next); + regoptail(preg, ret, next); + } + preg->regparse++; + if (ISMULT(*preg->regparse)) { + preg->err = REG_ERR_NESTED_COUNT; + return NULL; + } + + return(ret); +} + +static void reg_addrange(regex_t *preg, int lower, int upper) +{ + if (lower > upper) { + reg_addrange(preg, upper, lower); + } + while (lower <= upper) { + regc(preg, lower++); + } +} + +static void reg_addstr(regex_t *preg, const char *str) +{ + while (*str) { + regc(preg, *str++); + } +} + +/* + - regatom - the lowest level + * + * Optimization: gobbles an entire sequence of ordinary characters so that + * it can turn them into a single node, which is smaller to store and + * faster to run. Backslashed characters are exceptions, each becoming a + * separate node; the code is simpler that way and it's not worth fixing. + */ +static char *regatom(regex_t *preg, int *flagp ) +{ + char *ret; + int flags; + + *flagp = WORST; /* Tentatively. */ + + switch (*preg->regparse++) { + /* FIXME: these chars only have meaning at beg/end of pat? */ + case '^': + ret = regnode(preg, BOL); + break; + case '$': + ret = regnode(preg, EOL); + break; + case '.': + ret = regnode(preg, ANY); + *flagp |= HASWIDTH|SIMPLE; + break; + case '[': { + if (*preg->regparse == '^') { /* Complement of range. */ + ret = regnode(preg, ANYBUT); + preg->regparse++; + } else + ret = regnode(preg, ANYOF); + if (*preg->regparse == ']' || *preg->regparse == '-') + regc(preg, *preg->regparse++); + while (*preg->regparse != '\0' && *preg->regparse != ']') { + if (*preg->regparse == '-') { + preg->regparse++; + if (*preg->regparse == ']' || *preg->regparse == '\0') + regc(preg, '-'); + else { + reg_addrange(preg, UCHARAT(preg->regparse-2), UCHARAT(preg->regparse)); + preg->regparse++; + } + } else if (strncmp(preg->regparse, "[:alpha:]", 9) == 0) { + reg_addrange(preg,'a','z'); + reg_addrange(preg,'A','Z'); + preg->regparse += 9; + } else if (strncmp(preg->regparse, "[:alnum:]", 9) == 0) { + reg_addrange(preg,'a','z'); + reg_addrange(preg,'A','Z'); + reg_addrange(preg,'0','9'); + preg->regparse += 9; + } else if (strncmp(preg->regparse, "[:space:]", 9) == 0) { + reg_addstr(preg," \t\r\n\f\v"); + preg->regparse += 9; + } else + regc(preg, *preg->regparse++); + } + regc(preg, '\0'); + if (*preg->regparse == ']') { + preg->regparse++; + } + *flagp |= HASWIDTH|SIMPLE; + } + break; + case '(': + ret = reg(preg, 1, &flags); + if (ret == NULL) + return(NULL); + *flagp |= flags&(HASWIDTH|SPSTART); + break; + case '\0': + case '|': + case '\n': + case ')': + preg->err = REG_ERR_INTERNAL; + return NULL; /* Supposed to be caught earlier. */ + case '?': + case '+': + case '*': + preg->err = REG_ERR_COUNT_FOLLOWS_NOTHING; + return NULL; + case '{': + preg->err = REG_ERR_COUNT_UNSUPPORTED; + return NULL; + case '\\': + switch (*preg->regparse++) { + case '\0': + preg->err = REG_ERR_TRAILING_BACKSLASH; + return NULL; + break; + case '<': + ret = regnode(preg, WORDA); + break; + case '>': + ret = regnode(preg, WORDZ); + break; + case 'd': + ret = regnode(preg, ANYOF); + reg_addrange(preg, '0', '9'); + regc(preg, '\0'); + *flagp |= HASWIDTH|SIMPLE; + break; + case 'w': + ret = regnode(preg, ANYOF); + reg_addrange(preg, 'a', 'z'); + reg_addrange(preg, 'A', 'Z'); + reg_addrange(preg, '0', '9'); + regc(preg, '_'); + regc(preg, '\0'); + *flagp |= HASWIDTH|SIMPLE; + break; + case 's': + ret = regnode(preg, ANYOF); + reg_addstr(preg," \t\r\n\f\v"); + regc(preg, '\0'); + *flagp |= HASWIDTH|SIMPLE; + break; + /* FIXME: Someday handle \1, \2, ... */ + default: + /* Handle general quoted chars in exact-match routine */ + goto de_fault; + } + break; + de_fault: + default: + /* + * Encode a string of characters to be matched exactly. + * + * This is a bit tricky due to quoted chars and due to + * '*', '+', and '?' taking the SINGLE char previous + * as their operand. + * + * On entry, the char at regparse[-1] is going to go + * into the string, no matter what it is. (It could be + * following a \ if we are entered from the '\' case.) + * + * Basic idea is to pick up a good char in ch and + * examine the next char. If it's *+? then we twiddle. + * If it's \ then we frozzle. If it's other magic char + * we push ch and terminate the string. If none of the + * above, we push ch on the string and go around again. + * + * regprev is used to remember where "the current char" + * starts in the string, if due to a *+? we need to back + * up and put the current char in a separate, 1-char, string. + * When regprev is NULL, ch is the only char in the + * string; this is used in *+? handling, and in setting + * flags |= SIMPLE at the end. + */ + { + const char *regprev; + char ch; + + preg->regparse--; /* Look at cur char */ + ret = regnode(preg, EXACTLY); + for ( regprev = 0 ; ; ) { + ch = *preg->regparse++; /* Get current char */ + switch (*preg->regparse) { /* look at next one */ + + default: + regc(preg, ch); /* Add cur to string */ + break; + + case '.': case '[': case '(': + case ')': case '|': case '\n': + case '$': case '^': + case '\0': + /* FIXME, $ and ^ should not always be magic */ + magic: + regc(preg, ch); /* dump cur char */ + goto done; /* and we are done */ + + case '?': case '+': case '*': + if (!regprev) /* If just ch in str, */ + goto magic; /* use it */ + /* End mult-char string one early */ + preg->regparse = regprev; /* Back up parse */ + goto done; + + case '\\': + regc(preg, ch); /* Cur char OK */ + switch (preg->regparse[1]){ /* Look after \ */ + case '\0': + case '<': + case '>': + /* FIXME: Someday handle \1, \2, ... */ + goto done; /* Not quoted */ + default: + /* Backup point is \, scan * point is after it. */ + regprev = preg->regparse; + preg->regparse++; + continue; /* NOT break; */ + } + } + regprev = preg->regparse; /* Set backup point */ + } + done: + regc(preg, '\0'); + *flagp |= HASWIDTH; + if (!regprev) /* One char? */ + *flagp |= SIMPLE; + } + break; + } + + return(ret); +} + +/* + - regnode - emit a node + */ +/* Location. */ +static char *regnode(regex_t *preg, int op) +{ + char *ret; + char *ptr; + + ret = preg->regcode; + if (ret == ®dummy) { + preg->regsize += 3; + return(ret); + } + + ptr = ret; + *ptr++ = op; + *ptr++ = '\0'; /* Null "next" pointer. */ + *ptr++ = '\0'; + preg->regcode = ptr; + + return(ret); +} + +/* + - regc - emit (if appropriate) a byte of code + */ +static void regc(regex_t *preg, int b ) +{ + if (preg->regcode != ®dummy) + *preg->regcode++ = b; + else + preg->regsize++; +} + +/* + - reginsert - insert an operator in front of already-emitted operand + * + * Means relocating the operand. + */ +static void reginsert(regex_t *preg, char op, char *opnd ) +{ + char *src; + char *dst; + char *place; + + if (preg->regcode == ®dummy) { + preg->regsize += 3; + return; + } + + src = preg->regcode; + preg->regcode += 3; + dst = preg->regcode; + while (src > opnd) + *--dst = *--src; + + place = opnd; /* Op node, where operand used to be. */ + *place++ = op; + *place++ = '\0'; + *place = '\0'; +} + +/* + - regtail - set the next-pointer at the end of a node chain + */ +static void regtail(regex_t *preg, char *p, const char *val ) +{ + char *scan; + char *temp; + int offset; + + if (p == ®dummy) + return; + + /* Find last node. */ + scan = p; + for (;;) { + temp = (char *)regnext(preg, scan); + if (temp == NULL) + break; + scan = temp; + } + + if (OP(scan) == BACK) + offset = scan - val; + else + offset = val - scan; + *(scan+1) = (offset>>8)&0377; + *(scan+2) = offset&0377; +} + +/* + - regoptail - regtail on operand of first argument; nop if operandless + */ + +static void regoptail(regex_t *preg, char *p, const char *val ) +{ + /* "Operandless" and "op != BRANCH" are synonymous in practice. */ + if (p == NULL || p == ®dummy || OP(p) != BRANCH) + return; + regtail(preg, OPERAND(p), val); +} + +/* + * regexec and friends + */ + +/* + * Forwards. + */ +static int regtry(regex_t *preg, const char *string ); +static int regmatch(regex_t *preg, const char *prog); +static int regrepeat(regex_t *preg, const char *p ); + +/* + - regexec - match a regexp against a string + */ +int regexec(regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags) +{ + const char *s; + + /* Be paranoid... */ + if (preg == NULL || preg->program == NULL || string == NULL) { + return REG_ERR_NULL_ARGUMENT; + } + + /* Check validity of program. */ + if (UCHARAT(preg->program) != REG_MAGIC) { + return REG_ERR_CORRUPTED; + } + +#ifdef DEBUG + /*regdump(preg);*/ +#endif + + preg->eflags = eflags; + preg->pmatch = pmatch; + preg->nmatch = nmatch; + preg->start = string; /* All offsets are computed from here */ + + /* If there is a "must appear" string, look for it. */ + if (preg->regmust != NULL) { + s = string; + while ((s = str_find(s, preg->regmust[0], preg->cflags & REG_ICASE)) != NULL) { + if (strncmp(s, preg->regmust, preg->regmlen) == 0) + break; /* Found it. */ + s++; + } + if (s == NULL) /* Not present. */ + return REG_NOMATCH; + } + + /* Mark beginning of line for ^ . */ + preg->regbol = string; + + /* Simplest case: anchored match need be tried only once (maybe per line). */ + if (preg->reganch) { + if (eflags & REG_NOTBOL) { + /* This is an anchored search, but not an BOL, so possibly skip to the next line */ + goto nextline; + } + while (1) { + int ret = regtry(preg, string); + if (ret) { + return REG_NOERROR; + } + if (*preg->reginput) { +nextline: + if (preg->cflags & REG_NEWLINE) { + /* Try the next anchor? */ + string = strchr(preg->reginput, '\n'); + if (string) { + preg->regbol = ++string; + continue; + } + } + } + return REG_NOMATCH; + } + } + + /* Messy cases: unanchored match. */ + s = string; + if (preg->regstart != '\0') { + /* We know what char it must start with. */ + while ((s = str_find(s, preg->regstart, preg->cflags & REG_ICASE)) != NULL) { + if (regtry(preg, s)) + return REG_NOERROR; + s++; + } + } + else + /* We don't -- general case. */ + do { + if (regtry(preg, s)) + return REG_NOERROR; + } while (*s++ != '\0'); + + /* Failure. */ + return REG_NOMATCH; +} + +/* + - regtry - try match at specific point + */ + /* 0 failure, 1 success */ +static int regtry( regex_t *preg, const char *string ) +{ + int i; + + preg->reginput = string; + + for (i = 0; i < preg->nmatch; i++) { + preg->pmatch[i].rm_so = -1; + preg->pmatch[i].rm_eo = -1; + } + if (regmatch(preg, preg->program + 1)) { + preg->pmatch[0].rm_so = string - preg->start; + preg->pmatch[0].rm_eo = preg->reginput - preg->start; + return(1); + } else + return(0); +} + +/** + * Returns 1 if 'pattern' is a prefix of 'string'. + * + * If 'nocase' is non-zero, does a case-insensitive match. + */ +static int prefix_cmp(const char *pattern, const char *string, int nocase) +{ + if (nocase) { + while (*pattern && *string) { + if (toupper(*pattern) != toupper(*string)) { + break; + } + pattern++; + string++; + } + return *pattern == 0; + } + else { + if (*pattern == *string) { + int len = strlen(pattern); + return strncmp(pattern, string, len) == 0; + } + return 0; + } +} + +static const char *str_find(const char *string, char c, int nocase) +{ + if (nocase) { + c = toupper(c); + while (*string) { + if (toupper(*string) == c) { + return string; + } + string++; + } + return NULL; + } + else { + return strchr(string, c); + } +} + +static int reg_iseol(regex_t *preg, int ch) +{ + if (preg->cflags & REG_NEWLINE) { + return ch == '\0' || ch == '\n'; + } + else { + return ch == '\0'; + } +} + +/* + - regmatch - main matching routine + * + * Conceptually the strategy is simple: check to see whether the current + * node matches, call self recursively to see whether the rest matches, + * and then act accordingly. In practice we make some effort to avoid + * recursion, in particular by going through "ordinary" nodes (that don't + * need to know whether the rest of the match failed) by a loop instead of + * by recursion. + */ +/* 0 failure, 1 success */ +static int regmatch(regex_t *preg, const char *prog) +{ + const char *scan; /* Current node. */ + const char *next; /* Next node. */ + + scan = prog; +#ifdef DEBUG + if (scan != NULL && regnarrate) + fprintf(stderr, "%s(\n", regprop(scan)); +#endif + while (scan != NULL) { +#ifdef DEBUG + if (regnarrate) + fprintf(stderr, "%s...\n", regprop(scan)); +#endif + next = regnext(preg, scan); + + switch (OP(scan)) { + case BOL: + if (preg->reginput != preg->regbol) + return(0); + break; + case EOL: + if (!reg_iseol(preg, *preg->reginput)) { + return(0); + } + break; + case WORDA: + /* Must be looking at a letter, digit, or _ */ + if ((!isalnum(*preg->reginput)) && *preg->reginput != '_') + return(0); + /* Prev must be BOL or nonword */ + if (preg->reginput > preg->regbol && + (isalnum(preg->reginput[-1]) || preg->reginput[-1] == '_')) + return(0); + break; + case WORDZ: + /* Must be looking at non letter, digit, or _ */ + if (isalnum(*preg->reginput) || *preg->reginput == '_') + return(0); + /* We don't care what the previous char was */ + break; + case ANY: + if (reg_iseol(preg, *preg->reginput)) + return 0; + preg->reginput++; + break; + case EXACTLY: { + const char *opnd; + + opnd = OPERAND(scan); + + if (prefix_cmp(opnd, preg->reginput, preg->cflags & REG_ICASE) == 0) { + return(0); + } + preg->reginput += strlen(opnd); + } + break; + case ANYOF: + if (reg_iseol(preg, *preg->reginput)) + return 0; + if (str_find(OPERAND(scan), *preg->reginput, preg->cflags & REG_ICASE) == NULL) + return(0); + preg->reginput++; + break; + case ANYBUT: + if (reg_iseol(preg, *preg->reginput)) + return 0; + if (str_find(OPERAND(scan), *preg->reginput, preg->cflags & REG_ICASE) != NULL) + return(0); + preg->reginput++; + break; + case NOTHING: + break; + case BACK: + break; + case BRANCH: { + const char *save; + + if (OP(next) != BRANCH) /* No choice. */ + next = OPERAND(scan); /* Avoid recursion. */ + else { + do { + save = preg->reginput; + if (regmatch(preg, OPERAND(scan))) + return(1); + preg->reginput = save; + scan = regnext(preg, scan); + } while (scan != NULL && OP(scan) == BRANCH); + return(0); + /* NOTREACHED */ + } + } + break; + case STAR: + case PLUS: { + char nextch; + int no; + const char *save; + int min; + + /* + * Lookahead to avoid useless match attempts + * when we know what character comes next. + */ + nextch = '\0'; + if (OP(next) == EXACTLY) + nextch = *OPERAND(next); + min = (OP(scan) == STAR) ? 0 : 1; + save = preg->reginput; + no = regrepeat(preg, OPERAND(scan)); + while (no >= min) { + /* If it could work, try it. */ + if (reg_iseol(preg, nextch) || *preg->reginput == nextch) + if (regmatch(preg, next)) + return(1); + /* Couldn't or didn't -- back up. */ + no--; + preg->reginput = save + no; + } + return(0); + } + break; + case END: + return(1); /* Success! */ + break; + default: + if (OP(scan) >= OPEN+1 && OP(scan) < CLOSE_END) { + const char *save; + + save = preg->reginput; + + if (regmatch(preg, next)) { + int no; + /* + * Don't set startp if some later + * invocation of the same parentheses + * already has. + */ + if (OP(scan) < CLOSE) { + no = OP(scan) - OPEN; + if (no < preg->nmatch && preg->pmatch[no].rm_so == -1) { + preg->pmatch[no].rm_so = save - preg->start; + } + } + else { + no = OP(scan) - CLOSE; + if (no < preg->nmatch && preg->pmatch[no].rm_eo == -1) { + preg->pmatch[no].rm_eo = save - preg->start; + } + } + return(1); + } else + return(0); + } + return REG_ERR_INTERNAL; + } + + scan = next; + } + + /* + * We get here only if there's trouble -- normally "case END" is + * the terminating point. + */ + return REG_ERR_INTERNAL; +} + +/* + - regrepeat - repeatedly match something simple, report how many + */ +static int regrepeat(regex_t *preg, const char *p ) +{ + int count = 0; + const char *scan; + const char *opnd; + + scan = preg->reginput; + opnd = OPERAND(p); + switch (OP(p)) { + case ANY: + while (!reg_iseol(preg, *scan)) { + count++; + scan++; + } + break; + case EXACTLY: + if (preg->cflags & REG_ICASE) { + while (toupper(*opnd) == toupper(*scan)) { + count++; + scan++; + } + } + else { + while (*opnd == *scan) { + count++; + scan++; + } + } + break; + case ANYOF: + while (!reg_iseol(preg, *scan) && str_find(opnd, *scan, preg->cflags & REG_ICASE) != NULL) { + count++; + scan++; + } + break; + case ANYBUT: + while (!reg_iseol(preg, *scan) && str_find(opnd, *scan, preg->cflags & REG_ICASE) == NULL) { + count++; + scan++; + } + break; + default: /* Oh dear. Called inappropriately. */ + preg->err = REG_ERR_INTERNAL; + count = 0; /* Best compromise. */ + break; + } + preg->reginput = scan; + + return(count); +} + +/* + - regnext - dig the "next" pointer out of a node + */ +static const char *regnext(regex_t *preg, const char *p ) +{ + int offset; + + if (p == ®dummy) + return(NULL); + + offset = NEXT(p); + if (offset == 0) + return(NULL); + + if (OP(p) == BACK) + return(p-offset); + else + return(p+offset); +} + +#ifdef DEBUG + +/* + - regdump - dump a regexp onto stdout in vaguely comprehensible form + */ +static void regdump(regex_t *preg) +{ + const char *s; + char op = EXACTLY; /* Arbitrary non-END op. */ + const char *next; + + + s = preg->program + 1; + while (op != END) { /* While that wasn't END last time... */ + op = OP(s); + printf("%2d%s", (int)(s-preg->program), regprop(s)); /* Where, what. */ + next = regnext(preg, s); + if (next == NULL) /* Next ptr. */ + printf("(0)"); + else + printf("(%d)", (int)((s-preg->program)+(next-s))); + s += 3; + if (op == ANYOF || op == ANYBUT || op == EXACTLY) { + /* Literal string, where present. */ + while (*s != '\0') { + putchar(*s); + s++; + } + s++; + } + putchar('\n'); + } + + /* Header fields of interest. */ + if (preg->regstart != '\0') + printf("start `%c' ", preg->regstart); + if (preg->reganch) + printf("anchored "); + if (preg->regmust != NULL) + printf("must have \"%s\"", preg->regmust); + printf("\n"); +} + +/* + - regprop - printable representation of opcode + */ +static const char *regprop( const char *op ) +{ + char *p; + static char buf[50]; + + (void) strcpy(buf, ":"); + + switch (OP(op)) { + case BOL: + p = "BOL"; + break; + case EOL: + p = "EOL"; + break; + case ANY: + p = "ANY"; + break; + case ANYOF: + p = "ANYOF"; + break; + case ANYBUT: + p = "ANYBUT"; + break; + case BRANCH: + p = "BRANCH"; + break; + case EXACTLY: + p = "EXACTLY"; + break; + case NOTHING: + p = "NOTHING"; + break; + case BACK: + p = "BACK"; + break; + case END: + p = "END"; + break; + case STAR: + p = "STAR"; + break; + case PLUS: + p = "PLUS"; + break; + case WORDA: + p = "WORDA"; + break; + case WORDZ: + p = "WORDZ"; + break; + default: + if (OP(op) >= OPEN && OP(op) < CLOSE) { + sprintf(buf+strlen(buf), "OPEN%d", OP(op)-OPEN); + } + else if (OP(op) >= CLOSE && OP(op) < CLOSE_END) { + sprintf(buf+strlen(buf), "CLOSE%d", OP(op)-CLOSE); + } + else { + abort(); + } + p = NULL; + break; + } + if (p != NULL) + (void) strcat(buf, p); + return(buf); +} +#endif + +size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size) +{ + static const char *error_strings[] = { + "success", + "no match", + "bad pattern", + "null argument", + "unknown error", + "too big", + "out of memory", + "too many ()", + "parentheses () not balanced", + "extra characters", + "*+ of empty atom", + "nested count", + "internal error", + "count follows nothing", + "trailing backslash", + "corrupted program", + "braces {} not supported" + }; + const char *err; + + if (errcode < 0 || errcode >= REG_ERR_NUM) { + err = "Bad error code"; + } + else { + err = error_strings[errcode]; + } + + return snprintf(errbuf, errbuf_size, "%s", err); +} + +void regfree(regex_t *preg) +{ + free(preg->program); +} + +#endif |