1 /*
2 * Copyright (C) 1984-2026 Mark Nudelman
3 *
4 * You may distribute under the terms of either the GNU General Public
5 * License or the Less License, as specified in the README file.
6 *
7 * For more information, see the README file.
8 */
9
10 /*
11 * Routines to do pattern matching.
12 */
13
14 #include "less.h"
15
16 extern int caseless;
17 extern int is_caseless;
18 extern int utf_mode;
19
20 /*
21 * Compile a search pattern, for future use by match_pattern.
22 */
compile_pattern2(constant char * pattern,int search_type,PATTERN_TYPE * comp_pattern,int show_error)23 static int compile_pattern2(constant char *pattern, int search_type, PATTERN_TYPE *comp_pattern, int show_error)
24 {
25 if (search_type & SRCH_NO_REGEX)
26 return (0);
27 {
28 #if HAVE_GNU_REGEX
29 struct re_pattern_buffer *comp = (struct re_pattern_buffer *)
30 ecalloc(1, sizeof(struct re_pattern_buffer));
31 re_set_syntax(RE_SYNTAX_POSIX_EXTENDED);
32 if (re_compile_pattern(pattern, strlen(pattern), comp))
33 {
34 free(comp);
35 if (show_error)
36 error("Invalid pattern", NULL_PARG);
37 return (-1);
38 }
39 if (*comp_pattern != NULL)
40 {
41 regfree(*comp_pattern);
42 free(*comp_pattern);
43 }
44 *comp_pattern = comp;
45 #endif
46 #if HAVE_POSIX_REGCOMP
47 regex_t *comp = (regex_t *) ecalloc(1, sizeof(regex_t));
48 if (regcomp(comp, pattern, REGCOMP_FLAG | (is_caseless ? REG_ICASE : 0)))
49 {
50 free(comp);
51 if (show_error)
52 error("Invalid pattern", NULL_PARG);
53 return (-1);
54 }
55 if (*comp_pattern != NULL)
56 {
57 regfree(*comp_pattern);
58 free(*comp_pattern);
59 }
60 *comp_pattern = comp;
61 #endif
62 #if HAVE_PCRE
63 constant char *errstring;
64 int erroffset;
65 PARG parg;
66 pcre *comp = pcre_compile(pattern,
67 ((utf_mode) ? PCRE_UTF8 | PCRE_NO_UTF8_CHECK : 0) |
68 (is_caseless ? PCRE_CASELESS : 0),
69 &errstring, &erroffset, NULL);
70 if (comp == NULL)
71 {
72 parg.p_string = (char *) errstring;
73 if (show_error)
74 error("%s", &parg);
75 return (-1);
76 }
77 *comp_pattern = comp;
78 #endif
79 #if HAVE_PCRE2
80 int errcode;
81 PCRE2_SIZE erroffset;
82 PARG parg;
83 pcre2_code *comp = pcre2_compile((PCRE2_SPTR)pattern, strlen(pattern),
84 ((utf_mode) ? PCRE2_UTF | PCRE2_NO_UTF_CHECK : 0) |
85 (is_caseless ? PCRE2_CASELESS : 0),
86 &errcode, &erroffset, NULL);
87 if (comp == NULL)
88 {
89 if (show_error)
90 {
91 char msg[160];
92 pcre2_get_error_message(errcode, (PCRE2_UCHAR*)msg, sizeof(msg));
93 parg.p_string = msg;
94 error("%s", &parg);
95 }
96 return (-1);
97 }
98 *comp_pattern = comp;
99 #endif
100 #if HAVE_RE_COMP
101 PARG parg;
102 if ((parg.p_string = re_comp(pattern)) != NULL)
103 {
104 if (show_error)
105 error("%s", &parg);
106 return (-1);
107 }
108 *comp_pattern = 1;
109 #endif
110 #if HAVE_REGCMP
111 char *comp;
112 if ((comp = regcmp(pattern, 0)) == NULL)
113 {
114 if (show_error)
115 error("Invalid pattern", NULL_PARG);
116 return (-1);
117 }
118 if (comp_pattern != NULL)
119 free(*comp_pattern);
120 *comp_pattern = comp;
121 #endif
122 #if HAVE_V8_REGCOMP
123 struct regexp *comp;
124 reg_show_error = show_error;
125 comp = regcomp(pattern);
126 reg_show_error = 1;
127 if (comp == NULL)
128 {
129 /*
130 * regcomp has already printed an error message
131 * via regerror().
132 */
133 return (-1);
134 }
135 if (*comp_pattern != NULL)
136 free(*comp_pattern);
137 *comp_pattern = comp;
138 #endif
139 }
140 return (0);
141 }
142
143 /*
144 * Like compile_pattern2, but convert the pattern to lowercase if necessary.
145 */
compile_pattern(constant char * pattern,int search_type,int show_error,PATTERN_TYPE * comp_pattern)146 public int compile_pattern(constant char *pattern, int search_type, int show_error, PATTERN_TYPE *comp_pattern)
147 {
148 int result;
149
150 #if RE_HANDLES_CASELESS
151 if (caseless != OPT_ONPLUS || (!(search_type & SRCH_NO_REGEX)))
152 #else
153 if (caseless != OPT_ONPLUS)
154 #endif
155 {
156 result = compile_pattern2(pattern, search_type, comp_pattern, show_error);
157 } else
158 {
159 char *cvt_pattern = (char*) ecalloc(1, cvt_length(strlen(pattern), CVT_TO_LC));
160 cvt_text(cvt_pattern, pattern, NULL, NULL, CVT_TO_LC);
161 result = compile_pattern2(cvt_pattern, search_type, comp_pattern, show_error);
162 free(cvt_pattern);
163 }
164 return (result);
165 }
166
167 /*
168 * Forget that we have a compiled pattern.
169 */
uncompile_pattern(PATTERN_TYPE * pattern)170 public void uncompile_pattern(PATTERN_TYPE *pattern)
171 {
172 #if HAVE_GNU_REGEX
173 if (*pattern != NULL)
174 {
175 regfree(*pattern);
176 free(*pattern);
177 }
178 *pattern = NULL;
179 #endif
180 #if HAVE_POSIX_REGCOMP
181 if (*pattern != NULL)
182 {
183 regfree(*pattern);
184 free(*pattern);
185 }
186 *pattern = NULL;
187 #endif
188 #if HAVE_PCRE
189 if (*pattern != NULL)
190 pcre_free(*pattern);
191 *pattern = NULL;
192 #endif
193 #if HAVE_PCRE2
194 if (*pattern != NULL)
195 pcre2_code_free(*pattern);
196 *pattern = NULL;
197 #endif
198 #if HAVE_RE_COMP
199 *pattern = 0;
200 #endif
201 #if HAVE_REGCMP
202 if (*pattern != NULL)
203 free(*pattern);
204 *pattern = NULL;
205 #endif
206 #if HAVE_V8_REGCOMP
207 if (*pattern != NULL)
208 free(*pattern);
209 *pattern = NULL;
210 #endif
211 }
212
213 #if 0
214 /*
215 * Can a pattern be successfully compiled?
216 */
217 public int valid_pattern(char *pattern)
218 {
219 PATTERN_TYPE comp_pattern;
220 int result;
221
222 SET_NULL_PATTERN(comp_pattern);
223 result = compile_pattern2(pattern, 0, &comp_pattern, 0);
224 if (result != 0)
225 return (0);
226 uncompile_pattern(&comp_pattern);
227 return (1);
228 }
229 #endif
230
231 /*
232 * Is a compiled pattern null?
233 */
is_null_pattern(PATTERN_TYPE pattern)234 public lbool is_null_pattern(PATTERN_TYPE pattern)
235 {
236 #if HAVE_GNU_REGEX
237 return (pattern == NULL);
238 #endif
239 #if HAVE_POSIX_REGCOMP
240 return (pattern == NULL);
241 #endif
242 #if HAVE_PCRE
243 return (pattern == NULL);
244 #endif
245 #if HAVE_PCRE2
246 return (pattern == NULL);
247 #endif
248 #if HAVE_RE_COMP
249 return (pattern == 0);
250 #endif
251 #if HAVE_REGCMP
252 return (pattern == NULL);
253 #endif
254 #if HAVE_V8_REGCOMP
255 return (pattern == NULL);
256 #endif
257 #if NO_REGEX
258 return (pattern == NULL);
259 #endif
260 }
261 /*
262 * Simple pattern matching function.
263 * It supports no metacharacters like *, etc.
264 */
match(constant char * pattern,size_t pattern_len,constant char * buf,int buf_len,constant char *** sp,constant char *** ep,int nsubs)265 static int match(constant char *pattern, size_t pattern_len, constant char *buf, int buf_len, constant char ***sp, constant char ***ep, int nsubs)
266 {
267 constant char *pp;
268 constant char *lp;
269 constant char *pattern_end = pattern + pattern_len;
270 constant char *buf_end = buf + buf_len;
271
272 (void) nsubs;
273 for ( ; buf < buf_end; buf++)
274 {
275 for (pp = pattern, lp = buf; ; pp++, lp++)
276 {
277 char cp = *pp;
278 char cl = *lp;
279 if (caseless == OPT_ONPLUS && ASCII_IS_UPPER(cp))
280 cp = ASCII_TO_LOWER(cp);
281 if (cp != cl)
282 break;
283 if (pp == pattern_end || lp == buf_end)
284 break;
285 }
286 if (pp == pattern_end)
287 {
288 *(*sp)++ = buf;
289 *(*ep)++ = lp;
290 return (1);
291 }
292 }
293 **sp = **ep = NULL;
294 return (0);
295 }
296
297 /*
298 * Perform a pattern match with the previously compiled pattern.
299 * Set sp[0] and ep[0] to the start and end of the matched string.
300 * Set sp[i] and ep[i] to the start and end of the i-th matched subpattern.
301 * Subpatterns are defined by parentheses in the regex language.
302 */
match_pattern1(PATTERN_TYPE pattern,constant char * tpattern,constant char * line,size_t aline_len,size_t line_off,constant char ** sp,constant char ** ep,int nsp,int notbol,int search_type)303 static lbool match_pattern1(PATTERN_TYPE pattern, constant char *tpattern, constant char *line, size_t aline_len, size_t line_off, constant char **sp, constant char **ep, int nsp, int notbol, int search_type)
304 {
305 int matched;
306 int line_len = (int) aline_len; /*{{type-issue}}*/
307
308 #if NO_REGEX
309 search_type |= SRCH_NO_REGEX;
310 #endif
311 if (search_type & SRCH_NO_REGEX)
312 matched = match(tpattern, strlen(tpattern), line + line_off, line_len - line_off, &sp, &ep, nsp);
313 else
314 {
315 #if HAVE_GNU_REGEX
316 {
317 struct re_registers search_regs;
318 pattern->not_bol = notbol;
319 pattern->regs_allocated = REGS_UNALLOCATED;
320 matched = re_search(pattern, line, line_len, line_off, line_len - line_off, &search_regs) >= 0;
321 if (matched)
322 {
323 *sp++ = line + search_regs.start[0];
324 *ep++ = line + search_regs.end[0];
325 }
326 }
327 #endif
328 #if HAVE_POSIX_REGCOMP
329 {
330 #define RM_COUNT (NUM_SEARCH_COLORS+2)
331 regmatch_t rm[RM_COUNT];
332 int flags = (notbol) ? REG_NOTBOL : 0;
333 #ifdef REG_STARTEND
334 flags |= REG_STARTEND;
335 rm[0].rm_so = line_off;
336 rm[0].rm_eo = line_len;
337 #else
338 line += line_off;
339 #endif
340 matched = !regexec(pattern, line, RM_COUNT, rm, flags);
341 if (matched)
342 {
343 int i;
344 int ecount;
345 for (ecount = RM_COUNT; ecount > 0; ecount--)
346 if (rm[ecount-1].rm_so >= 0)
347 break;
348 if (ecount >= nsp)
349 ecount = nsp-1;
350 for (i = 0; i < ecount; i++)
351 {
352 if (rm[i].rm_so < 0)
353 {
354 *sp++ = *ep++ = line;
355 } else
356 {
357 #ifndef __WATCOMC__
358 *sp++ = line + rm[i].rm_so;
359 *ep++ = line + rm[i].rm_eo;
360 #else
361 *sp++ = rm[i].rm_sp;
362 *ep++ = rm[i].rm_ep;
363 #endif
364 }
365 }
366 }
367 }
368 #endif
369 #if HAVE_PCRE
370 {
371 #define OVECTOR_COUNT ((3*NUM_SEARCH_COLORS)+3)
372 int ovector[OVECTOR_COUNT];
373 int flags = (notbol) ? PCRE_NOTBOL : 0;
374 int i;
375 int ecount;
376 int mcount = pcre_exec(pattern, NULL, line, line_len,
377 line_off, flags, ovector, OVECTOR_COUNT);
378 matched = (mcount > 0);
379 ecount = nsp-1;
380 if (ecount > mcount) ecount = mcount;
381 for (i = 0; i < ecount*2; )
382 {
383 if (ovector[i] < 0 || ovector[i+1] < 0)
384 {
385 *sp++ = *ep++ = line;
386 i += 2;
387 } else
388 {
389 *sp++ = line + ovector[i++];
390 *ep++ = line + ovector[i++];
391 }
392 }
393 }
394 #endif
395 #if HAVE_PCRE2
396 {
397 int flags = (notbol) ? PCRE2_NOTBOL : 0;
398 pcre2_match_data *md = pcre2_match_data_create_from_pattern(pattern, NULL);
399 int mcount = pcre2_match(pattern, (PCRE2_SPTR)line, line_len,
400 line_off, flags, md, NULL);
401 matched = (mcount > 0);
402 if (matched)
403 {
404 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(md);
405 int i;
406 int ecount = nsp-1;
407 if (ecount > mcount) ecount = mcount;
408 for (i = 0; i < ecount*2; )
409 {
410 if (ovector[i] < 0 || ovector[i+1] < 0)
411 {
412 *sp++ = *ep++ = line;
413 i += 2;
414 } else
415 {
416 *sp++ = line + ovector[i++];
417 *ep++ = line + ovector[i++];
418 }
419 }
420 }
421 pcre2_match_data_free(md);
422 }
423 #endif
424 #if HAVE_RE_COMP
425 matched = (re_exec(line + line_off) == 1);
426 /*
427 * re_exec doesn't seem to provide a way to get the matched string.
428 */
429 #endif
430 #if HAVE_REGCMP
431 matched = ((*ep++ = regex(pattern, line + line_off)) != NULL);
432 if (matched)
433 *sp++ = __loc1;
434 #endif
435 #if HAVE_V8_REGCOMP
436 #if HAVE_REGEXEC2
437 matched = regexec2(pattern, line + line_off, notbol);
438 #else
439 matched = regexec(pattern, line + line_off);
440 #endif
441 if (matched)
442 {
443 *sp++ = pattern->startp[0];
444 *ep++ = pattern->endp[0];
445 }
446 #endif
447 }
448 *sp = *ep = NULL;
449 matched = (!(search_type & SRCH_NO_MATCH) && matched) ||
450 ((search_type & SRCH_NO_MATCH) && !matched);
451 return (matched != 0);
452 }
453
454 /*
455 * Return TRUE if the match satisfies all SUBSEARCH conditions.
456 */
subsearch_ok(constant char ** sp,constant char ** ep,int search_type)457 static lbool subsearch_ok(constant char **sp, constant char **ep, int search_type)
458 {
459 int i;
460 for (i = 1; i <= NUM_SEARCH_COLORS; i++)
461 {
462 if ((search_type & SRCH_SUBSEARCH(i)) && ep[i] == sp[i])
463 return FALSE;
464 }
465 return TRUE;
466 }
467
match_pattern(PATTERN_TYPE pattern,constant char * tpattern,constant char * line,size_t line_len,size_t line_off,constant char ** sp,constant char ** ep,int nsp,int notbol,int search_type)468 public lbool match_pattern(PATTERN_TYPE pattern, constant char *tpattern, constant char *line, size_t line_len, size_t line_off, constant char **sp, constant char **ep, int nsp, int notbol, int search_type)
469 {
470 for (;;)
471 {
472 size_t mlen;
473 lbool matched = match_pattern1(pattern, tpattern, line, line_len, line_off, sp, ep, nsp, notbol, search_type);
474 if (!matched || subsearch_ok(sp, ep, search_type))
475 return matched;
476 /* We have a match, but it does not satisfy all SUBSEARCH conditions.
477 * Continue searching after this match. */
478 mlen = ptr_diff(ep[0], line);
479 if (mlen == 0)
480 /* If the match is empty, we can't progress. */
481 return FALSE;
482 line += mlen;
483 line_len -= mlen;
484 notbol = 1;
485 }
486 }
487
488 /*
489 * Return the name of the pattern matching library.
490 */
pattern_lib_name(void)491 public constant char * pattern_lib_name(void)
492 {
493 #if HAVE_GNU_REGEX
494 return ("GNU");
495 #else
496 #if HAVE_POSIX_REGCOMP
497 return ("POSIX");
498 #else
499 #if HAVE_PCRE2
500 return ("PCRE2");
501 #else
502 #if HAVE_PCRE
503 return ("PCRE");
504 #else
505 #if HAVE_RE_COMP
506 return ("BSD");
507 #else
508 #if HAVE_REGCMP
509 return ("V8");
510 #else
511 #if HAVE_V8_REGCOMP
512 return ("Spencer V8");
513 #else
514 return ("no");
515 #endif
516 #endif
517 #endif
518 #endif
519 #endif
520 #endif
521 #endif
522 }
523