/* * Copyright (c) 2002 by The XFree86 Project, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Except as contained in this notice, the name of the XFree86 Project shall * not be used in advertising or otherwise to promote the sale, use or other * dealings in this Software without prior written authorization from the * XFree86 Project. * * Author: Paulo César Pereira de Andrade */ /* $XFree86: xc/programs/xedit/lisp/re/rec.c,v 1.3 2002/11/15 07:01:33 paulo Exp $ */ #include "rep.h" /* * Types */ /* Information used while compiling the intermediate format of the re. */ typedef struct _irec_info { unsigned char *ptr; /* Pointer in the given regex pattern */ unsigned char *end; /* End of regex pattern */ int flags; /* Compile flags */ rec_alt *alt; /* Toplevel first/single alternative */ rec_alt *palt; /* Current alternative being compiled */ rec_grp *pgrp; /* Current group, if any */ rec_pat *ppat; /* Current pattern, if any */ /* Number of open parenthesis, for error checking */ int nparens; int ngrps; /* Number of groups, for backreference */ int ecode; } irec_info; /* * Prototypes */ /* (i)ntermediate (r)egular (e)xpression (c)ompile * Generates an intermediate stage compiled regex from * the specified pattern argument. Basically builds an * intermediate data structure to analyse and do syntax * error checking. */ static void irec_simple_pattern(irec_info*, rec_pat_t); static void irec_literal_pattern(irec_info*, int); static void irec_case_literal_pattern(irec_info*, int); static void irec_open_group(irec_info*); static void irec_close_group(irec_info*); static void irec_range(irec_info*); static void irec_range_single(irec_info*, int); static void irec_range_complex(irec_info*, int, int); static void irec_escape(irec_info*); static void irec_simple_repetition(irec_info*, rec_rep_t); static void irec_complex_repetition(irec_info*); static void irec_add_repetition(irec_info*, rec_rep*); static void irec_free(irec_info*); static void irec_free_grp(rec_grp*); static void irec_free_pats(rec_pat*); /* * Implementation */ rec_alt * irec_comp(const char *pattern, const char *endp, int flags, int *ecode) { unsigned char *ptr; rec_alt *alt; irec_info inf; if (pattern == NULL || endp < pattern) { *ecode = RE_INVARG; return (NULL); } if (endp == pattern) { *ecode = RE_EMPTY; return (NULL); } alt = calloc(1, sizeof(rec_alt)); if (alt == NULL) { *ecode = RE_ESPACE; return (NULL); } inf.ptr = (unsigned char*)pattern; inf.end = (unsigned char*)endp; inf.flags = flags; inf.alt = inf.palt = alt; inf.pgrp = NULL; inf.ppat = NULL; inf.nparens = inf.ngrps = 0; inf.ecode = 0; if (flags & RE_NOSPEC) { /* Just searching for a character or substring */ for (; inf.ecode == 0 && inf.ptr < inf.end; inf.ptr++) { if (!(flags & RE_ICASE) || (!isupper(*inf.ptr) && !islower(*inf.ptr))) irec_literal_pattern(&inf, *inf.ptr); else irec_case_literal_pattern(&inf, *inf.ptr); } } /* inf.ptr = inf.end is nul if flags & RE_NOSPEC */ for (; inf.ecode == 0 && inf.ptr < inf.end;) { switch (*inf.ptr++) { case '*': irec_simple_repetition(&inf, Rer_AnyTimes); break; case '+': irec_simple_repetition(&inf, Rer_AtLeast); break; case '?': irec_simple_repetition(&inf, Rer_Maybe); break; case '.': irec_simple_pattern(&inf, Rep_Any); break; case '^': if (flags & RE_NEWLINE) /* It is up to the user decide if this can match */ irec_simple_pattern(&inf, Rep_Bol); else { for (ptr = inf.ptr - 1; ptr > (unsigned char*)pattern && *ptr == '('; ptr--) ; /* If at the start of a pattern */ if (ptr == (unsigned char*)pattern || *ptr == '|') irec_simple_pattern(&inf, Rep_Bol); else /* In the middle of a pattern, treat as literal */ irec_literal_pattern(&inf, '^'); } break; case '$': if (flags & RE_NEWLINE) irec_simple_pattern(&inf, Rep_Eol); else { /* Look ahead to check if is the last char of a group */ for (ptr = inf.ptr; ptr < inf.end && *ptr == ')'; ptr++) ; if (*ptr == '\0' || *ptr == '|') /* Last character of pattern, an EOL match */ irec_simple_pattern(&inf, Rep_Eol); else /* Normal character */ irec_literal_pattern(&inf, '$'); } break; case '(': irec_open_group(&inf); break; case ')': /* Look ahead to check if need to close the group now */ ptr = inf.ptr; if (*ptr != '*' && *ptr != '+' && *ptr != '?' && *ptr != '{') /* If a repetition does not follow */ irec_close_group(&inf); else if (inf.pgrp == NULL) /* A repetition follows, but current group is implicit */ inf.ecode = RE_EPAREN; else /* Can do this as next character is known */ inf.ppat = NULL; break; case '[': irec_range(&inf); break; case ']': irec_literal_pattern(&inf, ']'); break; case '{': irec_complex_repetition(&inf); break; case '}': irec_literal_pattern(&inf, '}'); break; case '|': /* If first character in the pattern */ if (inf.ptr - 1 == (unsigned char*)pattern || /* If last character in the pattern */ inf.ptr >= inf.end || /* If empty pattern */ inf.ptr[0] == '|' || inf.ptr[0] == ')') inf.ecode = RE_EMPTY; else { rec_alt *alt = calloc(1, sizeof(rec_alt)); if (alt) { alt->prev = inf.palt; inf.palt->next = alt; inf.palt = alt; inf.ppat = NULL; } else inf.ecode = RE_ESPACE; } break; case '\\': irec_escape(&inf); break; default: if (!(flags & RE_ICASE) || (!isupper(inf.ptr[-1]) && !islower(inf.ptr[-1]))) irec_literal_pattern(&inf, inf.ptr[-1]); else irec_case_literal_pattern(&inf, inf.ptr[-1]); break; } } /* Check if not all groups closed */ if (inf.ecode == 0 && inf.nparens) inf.ecode = RE_EPAREN; if (inf.ecode == 0) inf.ecode = orec_comp(inf.alt, flags); /* If an error generated */ if (inf.ecode) { irec_free(&inf); alt = NULL; } *ecode = inf.ecode; return (alt); } void irec_free_alt(rec_alt *alt) { rec_alt *next; while (alt) { next = alt->next; irec_free_pats(alt->pat); free(alt); alt = next; } } static void irec_simple_pattern(irec_info *inf, rec_pat_t type) { rec_pat *pat; /* Always add a new pattern to list */ if ((pat = calloc(1, sizeof(rec_pat))) == NULL) { inf->ecode = RE_ESPACE; return; } pat->type = type; if ((pat->prev = inf->ppat) != NULL) inf->ppat->next = pat; else inf->palt->pat = pat; inf->ppat = pat; } static void irec_literal_pattern(irec_info *inf, int value) { int length; rec_pat *pat; unsigned char chr, *str; /* If there is a current pattern */ if (inf->ppat && inf->ppat->rep == NULL) { switch (inf->ppat->type) { case Rep_Literal: /* Start literal string */ chr = inf->ppat->data.chr; if ((str = malloc(16)) == NULL) { inf->ecode = RE_ESPACE; return; } inf->ppat->type = Rep_String; inf->ppat->data.str = str; str[0] = chr; str[1] = value; str[2] = '\0'; return; case Rep_String: /* Augments literal string */ length = strlen((char*)inf->ppat->data.str); if ((length % 16) >= 14) { if ((str = realloc(inf->ppat->data.str, length + 18)) == NULL) { inf->ecode = RE_ESPACE; return; } inf->ppat->data.str = str; } inf->ppat->data.str[length] = value; inf->ppat->data.str[length + 1] = '\0'; return; default: /* Anything else is added as a new pattern list element */ break; } } if ((pat = calloc(1, sizeof(rec_pat))) == NULL) { inf->ecode = RE_ESPACE; return; } pat->type = Rep_Literal; pat->data.chr = value; if ((pat->prev = inf->ppat) != NULL) inf->ppat->next = pat; else inf->palt->pat = pat; inf->ppat = pat; } static void irec_case_literal_pattern(irec_info *inf, int value) { int length; rec_pat *pat; unsigned char plower, pupper, lower, upper, *str; lower = tolower(value); upper = toupper(value); /* If there is a current pattern */ if (inf->ppat && inf->ppat->rep == NULL) { switch (inf->ppat->type) { case Rep_CaseLiteral: /* Start case literal string */ plower = inf->ppat->data.cse.lower; pupper = inf->ppat->data.cse.upper; if ((str = malloc(32)) == NULL) { inf->ecode = RE_ESPACE; return; } inf->ppat->type = Rep_CaseString; inf->ppat->data.str = str; str[0] = plower; str[1] = pupper; str[2] = lower; str[3] = upper; str[4] = '\0'; return; case Rep_CaseString: /* Augments case literal string */ length = strlen((char*)inf->ppat->data.str); if (((length) % 32) >= 28) { if ((str = realloc(inf->ppat->data.str, length + 36)) == NULL) { inf->ecode = RE_ESPACE; return; } inf->ppat->data.str = str; } inf->ppat->data.str[length] = lower; inf->ppat->data.str[length + 1] = upper; inf->ppat->data.str[length + 2] = '\0'; return; default: /* Anything else is added as a new pattern list element */ break; } } if ((pat = calloc(1, sizeof(rec_pat))) == NULL) { inf->ecode = RE_ESPACE; return; } pat->type = Rep_CaseLiteral; pat->data.cse.lower = lower; pat->data.cse.upper = upper; pat->prev = inf->ppat; if ((pat->prev = inf->ppat) != NULL) inf->ppat->next = pat; else inf->palt->pat = pat; inf->ppat = pat; } static void irec_open_group(irec_info *inf) { rec_pat *pat; rec_alt *alt; rec_grp *grp; if ((grp = calloc(1, sizeof(rec_grp))) == NULL) { inf->ecode = RE_ESPACE; return; } if ((pat = calloc(1, sizeof(rec_pat))) == NULL) { free(grp); inf->ecode = RE_ESPACE; return; } if ((alt = calloc(1, sizeof(rec_alt))) == NULL) { free(grp); free(pat); inf->ecode = RE_ESPACE; return; } pat->type = Rep_Group; pat->data.grp = grp; grp->parent = pat; grp->palt = inf->palt; grp->pgrp = inf->pgrp; grp->alt = alt; grp->comp = 0; if ((pat->prev = inf->ppat) != NULL) inf->ppat->next = pat; else inf->palt->pat = pat; inf->palt = alt; inf->ppat = NULL; /* Only toplevel parenthesis supported */ if (++inf->nparens == 1) ++inf->ngrps; inf->pgrp = grp; } static void irec_close_group(irec_info *inf) { if (inf->pgrp == NULL) { inf->ecode = RE_EPAREN; return; } inf->palt = inf->pgrp->palt; inf->ppat = inf->pgrp->parent; inf->pgrp = inf->pgrp->pgrp; --inf->nparens; } static void irec_range(irec_info *inf) { int count; rec_pat *pat; rec_rng *rng; int not = inf->ptr[0] == '^'; if (not) ++inf->ptr; pat = calloc(1, sizeof(rec_pat)); if (pat == NULL) { inf->ecode = RE_ESPACE; return; } rng = calloc(1, sizeof(rec_rng)); if (pat == NULL) { free(pat); inf->ecode = RE_ESPACE; return; } pat->data.rng = rng; pat->type = not ? Rep_RangeNot : Rep_Range; if ((pat->prev = inf->ppat) != NULL) inf->ppat->next = pat; else inf->palt->pat = pat; inf->ppat = pat; /* First pass, add everything seen */ for (count = 0; inf->ecode == 0; count++) { /* If bracket not closed */ if (inf->ptr == inf->end) { inf->ecode = RE_EBRACK; return; } /* If not the first character */ else if (inf->ptr[0] == ']' && count) break; else { /* If not a range of characters */ if (inf->ptr[1] != '-' || inf->ptr[2] == ']') { irec_range_single(inf, inf->ptr[0]); ++inf->ptr; } else { if ((inf->flags & RE_NEWLINE) && inf->ptr[0] < '\n' && inf->ptr[2] > '\n') { /* Unless it is forced to be a delimiter, don't allow * a newline in a character range */ if (inf->ptr[0] == '\n' - 1) irec_range_single(inf, inf->ptr[0]); else irec_range_complex(inf, inf->ptr[0], '\n' - 1); if (inf->ptr[2] == '\n' + 1) irec_range_single(inf, inf->ptr[2]); else irec_range_complex(inf, '\n' + 1, inf->ptr[2]); } else irec_range_complex(inf, inf->ptr[0], inf->ptr[2]); inf->ptr += 3; } } } /* Skip ] */ ++inf->ptr; } static void irec_range_single(irec_info *inf, int value) { if (value >= 0 && value <= 255) inf->ppat->data.rng->range[value] = 1; if (inf->flags & RE_ICASE) { if (islower(value)) { value = toupper(value); if (value >= 0 && value <= 255) inf->ppat->data.rng->range[value] = 1; } else if (isupper(value)) { value = tolower(value); if (value >= 0 && value <= 255) inf->ppat->data.rng->range[value] = 1; } } } static void irec_range_complex(irec_info *inf, int chrf, int chrt) { if (chrf > chrt) { inf->ecode = RE_ERANGE; return; } for (; chrf <= chrt; chrf++) irec_range_single(inf, chrf); } static void irec_escape(irec_info *inf) { rec_pat *pat; unsigned char chr = inf->ptr[0]; if (chr == 0) { inf->ecode = RE_EESCAPE; return; } ++inf->ptr; switch (chr) { case 'o': irec_simple_pattern(inf, Rep_Odigit); break; case 'O': irec_simple_pattern(inf, Rep_OdigitNot); break; case 'd': irec_simple_pattern(inf, Rep_Digit); break; case 'D': irec_simple_pattern(inf, Rep_DigitNot); break; case 'x': irec_simple_pattern(inf, Rep_Xdigit); break; case 'X': irec_simple_pattern(inf, Rep_XdigitNot); break; case 's': irec_simple_pattern(inf, Rep_Space); break; case 'S': irec_simple_pattern(inf, Rep_SpaceNot); break; case 't': irec_simple_pattern(inf, Rep_Tab); break; case 'n': irec_simple_pattern(inf, Rep_Newline); break; case 'l': irec_simple_pattern(inf, Rep_Lower); break; case 'u': irec_simple_pattern(inf, Rep_Upper); break; case 'w': irec_simple_pattern(inf, Rep_Alnum); break; case 'W': irec_simple_pattern(inf, Rep_AlnumNot); break; case 'c': irec_simple_pattern(inf, Rep_Control); break; case 'C': irec_simple_pattern(inf, Rep_ControlNot); break; case '<': irec_simple_pattern(inf, Rep_Bow); break; case '>': irec_simple_pattern(inf, Rep_Eow); break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if ((inf->flags & RE_NOSUB) || (chr -= '1') >= inf->ngrps) { inf->ecode = RE_ESUBREG; return; } if ((pat = calloc(1, sizeof(rec_pat))) == NULL) { inf->ecode = RE_ESPACE; return; } pat->type = Rep_Backref; pat->data.chr = chr; pat->prev = inf->ppat; if (inf->ppat) inf->ppat->next = pat; else inf->palt->pat = pat; inf->ppat = pat; break; /* True literals */ case '0': irec_literal_pattern(inf, '\0'); break; case 'a': irec_literal_pattern(inf, '\a'); break; case 'b': irec_literal_pattern(inf, '\b'); break; case 'f': irec_literal_pattern(inf, '\f'); break; case 'r': irec_literal_pattern(inf, '\r'); break; case 'v': irec_literal_pattern(inf, '\v'); break; default: /* Don't check if case insensitive regular expression */ irec_literal_pattern(inf, chr); break; } } static void irec_simple_repetition(irec_info *inf, rec_rep_t type) { rec_rep *rep; /* If nowhere to add repetition */ if ((inf->pgrp == NULL && inf->ppat == NULL) || /* If repetition already added to last/current pattern */ (inf->pgrp == NULL && inf->ppat->rep != NULL) || /* If repetition already added to last/current group */ (inf->ppat == NULL && inf->pgrp->parent->rep != NULL)) { inf->ecode = RE_BADRPT; return; } if ((rep = calloc(1, sizeof(rec_rep))) == NULL) { inf->ecode = RE_ESPACE; return; } rep->type = type; irec_add_repetition(inf, rep); } static void irec_complex_repetition(irec_info *inf) { int exact; rec_rep *rep; long mine, maxc; unsigned char *end; /* If nowhere to add repetition */ if ((inf->pgrp == NULL && inf->ppat == NULL) || /* If repetition already added to last/current pattern */ (inf->pgrp == NULL && inf->ppat->rep != NULL) || /* If repetition already added to last/current group */ (inf->ppat == NULL && inf->pgrp->parent->rep != NULL)) { inf->ecode = RE_EBADBR; return; } exact = 0; mine = maxc = -1; if (inf->ptr[0] == ',') /* Specify max number of ocurrences only */ goto domax; else if (!isdigit(inf->ptr[0])) goto badbr; mine = strtol((char*)inf->ptr, (char**)&end, 10); inf->ptr = end; if (inf->ptr[0] == '}') { exact = 1; ++inf->ptr; goto redone; } else if (inf->ptr[0] != ',') goto badbr; domax: /* Add one to skip comma */ ++inf->ptr; if (inf->ptr[0] == '}') { ++inf->ptr; goto redone; } else if (!isdigit(inf->ptr[0])) goto badbr; maxc = strtol((char*)inf->ptr, (char**)&end, 10); inf->ptr = end; if (inf->ptr[0] != '}') goto badbr; ++inf->ptr; redone: if (mine == maxc) { maxc = -1; exact = 1; } /* Check range and if min-max parameters are valid */ if (mine >= 255 || maxc >= 255 || (mine >= 0 && maxc >= 0 && mine > maxc)) goto badbr; /* Check for noop */ if (exact && mine == 1) return; if ((rep = calloc(1, sizeof(rec_rep))) == NULL) { inf->ecode = RE_ESPACE; return; } /* Convert {0,1} to ? */ if (mine == 0 && maxc == 1) rep->type = Rer_Maybe; else if (exact) { rep->type = Rer_Exact; rep->mine = mine; } /* Convert {0,} to * */ else if (mine == 0 && maxc == -1) rep->type = Rer_AnyTimes; /* Convert {1,} to + */ else if (mine == 1 && maxc == -1) rep->type = Rer_AtLeast; else if (maxc == -1) { rep->type = Rer_Min; rep->mine = mine; } else if (mine < 1) { rep->type = Rer_Max; rep->maxc = maxc; } else { rep->type = Rer_MinMax; rep->mine = mine; rep->maxc = maxc; } irec_add_repetition(inf, rep); return; badbr: inf->ecode = RE_EBADBR; } /* The rep argument is allocated and has no reference yet, * if something fails it must be freed before returning. */ static void irec_add_repetition(irec_info *inf, rec_rep *rep) { int length; rec_pat *pat; rec_grp *grp; rec_rep_t rept; unsigned char value, upper; rept = rep->type; if (inf->ppat == NULL) { rec_pat *any; rec_grp *grp = inf->pgrp; if (rept == Rer_AnyTimes || rept == Rer_Maybe || rept == Re_AtLeast) { /* Convert (.)* to (.*), ((.))* not handled and may not match */ any = NULL; if (grp->alt && grp->alt->pat) { for (any = grp->alt->pat; any->next; any = any->next) ; switch (any->type) { case Rep_Any: break; case Rep_AnyAnyTimes: case Rep_AnyMaybe: case Rep_AnyAtLeast: free(rep); inf->ecode = RE_BADRPT; return; default: any = NULL; break; } } if (any) { free(rep); rep = NULL; any->type = (rept == Rer_AnyTimes) ? Rep_AnyAnyTimes : (rept == Rer_AtLeast) ? Rep_AnyAtLeast : Rep_AnyMaybe; while (grp) { ++grp->comp; grp = grp->pgrp; } } } inf->pgrp->parent->rep = rep; irec_close_group(inf); return; } switch (inf->ppat->type) { case Rep_Bol: case Rep_Eol: case Rep_Bow: case Rep_Eow: case Rep_AnyAnyTimes: case Rep_AnyMaybe: case Rep_AnyAtLeast: /* Markers that cannot repeat */ free(rep); inf->ecode = RE_BADRPT; return; case Rep_Any: grp = inf->pgrp; free(rep); if (rept == Rer_AnyTimes || rept == Rer_Maybe || rept == Rer_AtLeast) { inf->ppat->type = (rept == Rer_AnyTimes) ? Rep_AnyAnyTimes : (rept == Rer_Maybe) ? Rep_AnyMaybe : Rep_AnyAtLeast; while (grp) { ++grp->comp; grp = grp->pgrp; } } else /* XXX Not (yet) implemented */ inf->ecode = RE_BADRPT; rep = NULL; break; case Rep_String: if ((pat = calloc(1, sizeof(rec_pat))) == NULL) { free(rep); inf->ecode = RE_ESPACE; return; } length = strlen((char*)inf->ppat->data.str); pat->type = Rep_Literal; pat->prev = inf->ppat; pat->data.chr = inf->ppat->data.str[length - 1]; if (length == 2) { /* Must convert to two Rep_Literals */ value = inf->ppat->data.str[0]; free(inf->ppat->data.str); inf->ppat->data.chr = value; inf->ppat->type = Rep_Literal; } else /* Must remove last character from string */ inf->ppat->data.str[length - 1] = '\0'; inf->ppat->next = pat; inf->ppat = pat; break; case Rep_CaseString: if ((pat = calloc(1, sizeof(rec_pat))) == NULL) { free(rep); inf->ecode = RE_ESPACE; return; } length = strlen((char*)inf->ppat->data.str); pat->type = Rep_CaseLiteral; pat->prev = inf->ppat; pat->data.cse.lower = inf->ppat->data.str[length - 2]; pat->data.cse.upper = inf->ppat->data.str[length - 1]; if (length == 4) { /* Must convert to two Rep_CaseLiterals */ value = inf->ppat->data.str[0]; upper = inf->ppat->data.str[1]; free(inf->ppat->data.str); inf->ppat->data.cse.lower = value; inf->ppat->data.cse.upper = upper; inf->ppat->next = pat; inf->ppat->type = Rep_CaseLiteral; } else /* Must remove last character pair from string */ inf->ppat->data.str[length - 2] = '\0'; inf->ppat->next = pat; inf->ppat = pat; break; default: /* Anything else does not need special handling */ break; } inf->ppat->rep = rep; } static void irec_free(irec_info *inf) { irec_free_alt(inf->alt); } static void irec_free_grp(rec_grp *grp) { if (grp->alt) irec_free_alt(grp->alt); free(grp); } static void irec_free_pats(rec_pat *pat) { rec_pat *next; rec_pat_t rect; while (pat) { next = pat->next; if (pat->rep) free(pat->rep); rect = pat->type; if (rect == Rep_Range || rect == Rep_RangeNot) free(pat->data.rng); else if (rect == Rep_Group) irec_free_grp(pat->data.grp); else if (rect == Rep_StringList) orec_free_stl(pat->data.stl); free(pat); pat = next; } }