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: