Annotation of parser3/src/types/pa_vregex.C, revision 1.29

1.1       misha       1: /** @file
                      2:        Parser: @b regex class.
                      3: 
1.27      moko        4:        Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
1.22      moko        5:        Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
1.1       misha       6: */
                      7: 
                      8: #include "pa_vregex.h"
1.2       misha       9: #include "pa_vint.h"
1.8       misha      10: #include "pa_vstring.h"
1.29    ! moko       11: #include "pa_threads.h"
1.8       misha      12: 
1.29    ! moko       13: volatile const char * IDENT_PA_VREGEX_C="$Id: pa_vregex.C,v 1.28 2026/10/09 20:14:28 moko Exp $" IDENT_PA_VREGEX_H;
1.8       misha      14: 
                     15: // defines
                     16: 
                     17: #define REGEX_PATTERN_NAME "pattern"
                     18: #define REGEX_OPTIONS_NAME "options"
1.1       misha      19: 
1.11      moko       20: const char* get_pcre_exec_error_text(int exec_result){
1.24      moko       21:        if(exec_result == PCRE_ERROR_BADUTF8_OFFSET ||
                     22: #ifdef HAVE_PCRE2
                     23:                exec_result <= PCRE2_ERROR_UTF8_ERR1 /* -3 */ && exec_result >= PCRE2_ERROR_UTF8_ERR21 /* -21 */
                     24: #else
                     25:                exec_result == PCRE_ERROR_BADUTF8
                     26: #endif
                     27:        )
                     28:                return "UTF-8 validation failed during pcre_exec (%d).";
                     29:        return "execution error (%d)";
1.1       misha      30: }
                     31: 
                     32: 
1.29    ! moko       33: #ifdef REGEX_CACHE
        !            34: 
        !            35: #define REGEX_CACHE_BUCKETS 256
        !            36: #define REGEX_CACHE_LIMIT 512       // the cache is cleared when full to avoid unlimited growth
        !            37: #define REGEX_CACHE_MAX_PATTERN_LENGTH 512
        !            38: 
        !            39: /// pcre2_code is read-only while matching and can be used by many threads, while the match data belongs to VRegex
        !            40: class Regex_cache_entry: public PA_Object {
        !            41: public:
        !            42:        Charset* charset;
        !            43:        int options;
        !            44:        const char* pattern;
        !            45:        pcre2_code* code;
        !            46:        Regex_cache_entry* next;
        !            47: 
        !            48:        Regex_cache_entry(Charset* acharset, int aoptions, const char* apattern, pcre2_code* acode, Regex_cache_entry* anext):
        !            49:                charset(acharset), options(aoptions), pattern(pa_strdup(apattern)), code(acode), next(anext) {}
        !            50: };
        !            51: 
        !            52: static Regex_cache_entry* regex_cache[REGEX_CACHE_BUCKETS];
        !            53: static uint regex_cache_count=0;
        !            54: 
        !            55: /// the same pattern with other options or charset is rare and just shares the bucket
        !            56: static uint regex_cache_hash(const char* pattern){
        !            57:        uint result=0;
        !            58:        generic_hash_code(result, pattern);
        !            59:        return result % REGEX_CACHE_BUCKETS;
        !            60: }
        !            61: 
        !            62: static pcre2_code* regex_cache_get(uint bucket, Charset* charset, int options, const char* pattern){
        !            63:        SYNCHRONIZED;
        !            64:        for(Regex_cache_entry* e=regex_cache[bucket]; e; e=e->next)
        !            65:                if(e->charset==charset && e->options==options && strcmp(e->pattern, pattern)==0)
        !            66:                        return e->code;
        !            67:        return 0;
        !            68: }
        !            69: 
        !            70: static void regex_cache_put(uint bucket, Charset* charset, int options, const char* pattern, pcre2_code* code){
        !            71:        if(strlen(pattern)>REGEX_CACHE_MAX_PATTERN_LENGTH)
        !            72:                return; // longer patterns are not cached to conserve memory
        !            73: 
        !            74:        SYNCHRONIZED;
        !            75:        if(regex_cache_count>=REGEX_CACHE_LIMIT){
        !            76:                memset(regex_cache, 0, sizeof(regex_cache));
        !            77:                regex_cache_count=0;
        !            78:        }
        !            79:        regex_cache[bucket]=new Regex_cache_entry(charset, options, pattern, code, regex_cache[bucket]);
        !            80:        regex_cache_count++;
        !            81: }
        !            82: 
        !            83: #endif
        !            84: 
1.15      moko       85: Value& VRegex::as_expr_result() {
1.1       misha      86:        return *new VInt(as_int());
                     87: }
                     88: 
                     89: void VRegex::regex_options(const String* options, int* result){
                     90:        struct Regex_option {
                     91:                const char* key;
                     92:                const char* keyAlt;
                     93:                int clear;
                     94:                int set;
                     95:                int *result;
                     96:        } regex_option[]={
                     97:                {"i", "I", 0, PCRE_CASELESS, result}, // a=A
                     98:                {"s", "S", 0, PCRE_DOTALL, result}, // ^\n\n$ [default]
                     99:                {"m", "M", PCRE_DOTALL, PCRE_MULTILINE, result}, // ^aaa\n$^bbb\n$
                    100:                {"x", 0, 0, PCRE_EXTENDED, result}, // whitespace in regex ignored
                    101:                {"U", 0, 0, PCRE_UNGREEDY, result}, // ungreedy patterns (greedy by default)
                    102:                {"g", "G", 0, MF_GLOBAL_SEARCH, result+1}, // many rows
                    103:                {"'", 0, 0, MF_NEED_PRE_POST_MATCH, result+1},
                    104:                {"n", 0, 0, MF_JUST_COUNT_MATCHES, result+1},
                    105:                {0, 0, 0, 0, 0}
                    106:        };
                    107:        result[0]=PCRE_EXTRA /* backslash+non-special char causes error */
                    108:                        | PCRE_DOTALL /* dot matches all chars including newline char */
                    109:                        | PCRE_DOLLAR_ENDONLY /* dollar matches only end of string, but not newline chars */;
                    110:        result[1]=0;
                    111: 
1.11      moko      112:        if(options && !options->is_empty()){
1.12      misha     113:                size_t valid_options=0;
1.11      moko      114:                for(Regex_option *o=regex_option; o->key; o++)
1.1       misha     115:                        if(
                    116:                                options->pos(o->key)!=STRING_NOT_FOUND
                    117:                                || (o->keyAlt && options->pos(o->keyAlt)!=STRING_NOT_FOUND)
                    118:                        ){
                    119:                                *o->result &= ~o->clear;
                    120:                                *o->result |= o->set;
1.11      moko      121:                                valid_options++;
1.1       misha     122:                        }
1.11      moko      123:                if(options->length()!=valid_options)
                    124:                        throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
                    125:        }
1.1       misha     126: }
                    127: 
                    128: 
                    129: void VRegex::set(Charset& acharset, const String* aregex, const String* aoptions){
                    130:        if(aregex->is_empty())
1.20      moko      131:                throw Exception(PARSER_RUNTIME, 0, "regexp is empty");
1.1       misha     132: 
                    133:        fcharset=&acharset;
1.5       misha     134: 
1.7       misha     135:        fpattern=aregex->untaint_cstr(String::L_REGEX);
1.1       misha     136: 
1.20      moko      137:        foptions_cstr=aoptions ? aoptions->cstr() : 0;
1.8       misha     138: 
1.1       misha     139:        regex_options(aoptions, foptions);
                    140: }
                    141: 
                    142: 
1.20      moko      143: void VRegex::set(VRegex& avregex){
                    144:        fcharset=avregex.fcharset;
                    145: 
                    146:        fpattern=avregex.fpattern;
                    147: 
                    148:        foptions_cstr=avregex.foptions_cstr;
                    149: 
                    150:        regex_options(foptions_cstr ? new String(foptions_cstr) : 0, foptions);
                    151: }
                    152: 
                    153: 
1.1       misha     154: void VRegex::compile(){
                    155:        const char* err_ptr;
                    156:        int options=foptions[0];
                    157: 
                    158:        // @todo (for UTF-8): check string & pattern and use PCRE_NO_UTF8_CHECK option 
                    159:        if(fcharset->isUTF8())
1.13      misha     160:                options |= (PCRE_UTF8 | PCRE_UCP);
1.1       misha     161: 
1.24      moko      162: #ifdef HAVE_PCRE2
                    163:        int err;
                    164:        size_t err_offset;
                    165:        PCRE2_UCHAR buffer[120];
                    166: 
1.29    ! moko      167:        fmatch_data=0; // ^re.create[] of a created regex: sized for the previous pattern
        !           168: 
        !           169: #ifdef REGEX_CACHE
        !           170:        uint bucket=regex_cache_hash(fpattern);
        !           171:        if((fcode=regex_cache_get(bucket, fcharset, options, fpattern)))
        !           172:                return;
        !           173: #endif
        !           174: 
1.24      moko      175:        if(!fcmp_ctxt)
                    176:                fcmp_ctxt=pcre2_compile_context_create(fgen_ctxt);
                    177: 
                    178:        pcre2_set_character_tables(fcmp_ctxt, fcharset->pcre_tables);
                    179: 
                    180:        fcode=pcre2_compile((PCRE2_SPTR)fpattern, PCRE2_ZERO_TERMINATED, options,
                    181:                &err, &err_offset,
                    182:                fcmp_ctxt);
                    183: 
                    184:        if(!fcode){
                    185:                pcre2_get_error_message(err, buffer, sizeof(buffer));
                    186:                err_ptr=(const char*)buffer;
                    187:        }
1.29    ! moko      188: #ifdef REGEX_CACHE
        !           189:        else
        !           190:                regex_cache_put(bucket, fcharset, options, fpattern, fcode);
        !           191: #endif
1.24      moko      192: #else
                    193:        int err_offset;
1.28      moko      194:        fstudied=false; // ^re.create[] of a created regex: studied the previous pattern
1.1       misha     195:        fcode=pcre_compile(fpattern, options,
                    196:                &err_ptr, &err_offset,
                    197:                fcharset->pcre_tables);
1.24      moko      198: #endif
1.1       misha     199:        if(!fcode){
                    200:                throw Exception(PCRE_EXCEPTION_TYPE,
1.24      moko      201:                        new String(fpattern + (fpattern[err_offset] ? err_offset : 0), String::L_TAINTED),
1.1       misha     202:                        "regular expression syntax error - %s", err_ptr);
                    203:        }
                    204: 
                    205: }
                    206: 
                    207: 
                    208: size_t VRegex::full_info(int type){
                    209:        size_t result;
                    210:        int fullinfo_result=pcre_fullinfo(fcode, fextra, type, &result);
                    211:        if(fullinfo_result<0){
                    212:                throw Exception(PCRE_EXCEPTION_TYPE,
1.4       misha     213:                        new String(fpattern, String::L_TAINTED),
1.1       misha     214:                        "pcre_full_info error (%d)", fullinfo_result);
                    215:        }
                    216: 
                    217:        return result;
1.17      moko      218: }
1.1       misha     219: 
                    220: 
                    221: size_t VRegex::get_info_size(){
                    222:        return full_info(PCRE_INFO_SIZE);
                    223: }
                    224: 
                    225: 
                    226: size_t VRegex::get_study_size(){
1.24      moko      227: #ifdef HAVE_PCRE2
                    228:        return 0;
                    229: #else
1.1       misha     230:        return full_info(PCRE_INFO_STUDYSIZE);
1.24      moko      231: #endif
1.1       misha     232: }
                    233: 
1.24      moko      234: 
1.1       misha     235: void VRegex::study(){
1.24      moko      236: #ifndef HAVE_PCRE2
1.1       misha     237:        if(fstudied)
                    238:                return;
                    239: 
                    240:        const char* err_ptr;
                    241:        fextra=pcre_study(fcode, 0/*options*/, &err_ptr);
                    242: 
                    243:        if(err_ptr){
                    244:                throw Exception(PCRE_EXCEPTION_TYPE,
1.4       misha     245:                        new String(fpattern, String::L_TAINTED),
1.1       misha     246:                        "pcre_study error: %s", err_ptr);
                    247:        }
                    248: 
                    249:        fstudied=true;
1.24      moko      250: #endif
1.1       misha     251: }
                    252: 
                    253: 
                    254: int VRegex::exec(const char* string, size_t string_len, int* ovector, int ovector_size, int prestart){
1.24      moko      255: #ifdef HAVE_PCRE2
                    256:        if(!fmatch_ctxt)
                    257:                fmatch_ctxt=pcre2_match_context_create(fgen_ctxt);
                    258: 
                    259:        if(!fmatch_data)
                    260:                fmatch_data=pcre2_match_data_create_from_pattern(fcode, fgen_ctxt);
                    261: 
                    262:        int result=pcre2_match(fcode,
                    263:                (PCRE2_SPTR)string, string_len, prestart,
                    264:                prestart>0 ? PCRE2_NO_UTF_CHECK : 0, fmatch_data, fmatch_ctxt);
                    265: #else
1.1       misha     266:        int result=pcre_exec(fcode, fextra, 
                    267:                string, string_len, prestart,
1.10      misha     268:                prestart>0 ? PCRE_NO_UTF8_CHECK : 0, ovector, ovector_size);
1.24      moko      269: #endif
                    270: 
1.1       misha     271:        if(result<0 && result!=PCRE_ERROR_NOMATCH){
                    272:                throw Exception(PCRE_EXCEPTION_TYPE, 
1.4       misha     273:                        new String(fpattern, String::L_TAINTED),
1.1       misha     274:                        get_pcre_exec_error_text(result), result);
                    275:        }
                    276: 
1.24      moko      277: #ifdef HAVE_PCRE2
                    278:        if(result>0){
                    279:                result=min(result, ovector_size/3);
                    280:                size_t* groups=pcre2_get_ovector_pointer(fmatch_data);
                    281:                for(int i=0; i<result*2; i++){
                    282:                        ovector[i]=groups[i];
                    283:                }
                    284:        }
                    285: #endif
1.1       misha     286:        return result;
                    287: }
                    288: 
                    289: 
1.8       misha     290: Value* VRegex::get_element(const String& aname) { 
                    291:        if(aname == REGEX_PATTERN_NAME)
1.26      moko      292:                return new VString(fpattern);
1.8       misha     293: 
                    294:        if(aname == REGEX_OPTIONS_NAME)
1.26      moko      295:                return new VString(foptions_cstr);
1.8       misha     296: 
1.16      misha     297:        // method (if any)
1.8       misha     298:        if(Value* result=VStateless_object::get_element(aname))
                    299:                return result;
                    300: 
1.23      moko      301:        return bark("%s field not found", &aname);
1.8       misha     302: }

E-mail: