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