Ruby 3.5.0dev (2025-02-22 revision 412997300569c1853c09813e4924b6df3d7e8669)
regexp.c
1#include "prism/regexp.h"
2
3#define PM_REGEXP_PARSE_DEPTH_MAX 4096
4
8typedef struct {
11
13 const uint8_t *start;
14
16 const uint8_t *cursor;
17
19 const uint8_t *end;
20
26
29
32
35
37 void *name_data;
38
41
45
49static inline void
50pm_regexp_parse_error(pm_regexp_parser_t *parser, const uint8_t *start, const uint8_t *end, const char *message) {
51 parser->error_callback(start, end, message, parser->error_data);
52}
53
58static void
59pm_regexp_parser_named_capture(pm_regexp_parser_t *parser, const uint8_t *start, const uint8_t *end) {
60 pm_string_t string;
61 pm_string_shared_init(&string, start, end);
62 parser->name_callback(&string, parser->name_data);
63 pm_string_free(&string);
64}
65
69static inline bool
70pm_regexp_char_is_eof(pm_regexp_parser_t *parser) {
71 return parser->cursor >= parser->end;
72}
73
77static inline bool
78pm_regexp_char_accept(pm_regexp_parser_t *parser, uint8_t value) {
79 if (!pm_regexp_char_is_eof(parser) && *parser->cursor == value) {
80 parser->cursor++;
81 return true;
82 }
83 return false;
84}
85
89static inline bool
90pm_regexp_char_expect(pm_regexp_parser_t *parser, uint8_t value) {
91 if (!pm_regexp_char_is_eof(parser) && *parser->cursor == value) {
92 parser->cursor++;
93 return true;
94 }
95 return false;
96}
97
101static bool
102pm_regexp_char_find(pm_regexp_parser_t *parser, uint8_t value) {
103 if (pm_regexp_char_is_eof(parser)) {
104 return false;
105 }
106
107 const uint8_t *end = (const uint8_t *) pm_memchr(parser->cursor, value, (size_t) (parser->end - parser->cursor), parser->encoding_changed, parser->encoding);
108 if (end == NULL) {
109 return false;
110 }
111
112 parser->cursor = end + 1;
113 return true;
114}
115
149static bool
150pm_regexp_parse_range_quantifier(pm_regexp_parser_t *parser) {
151 const uint8_t *savepoint = parser->cursor;
152
153 enum {
154 PM_REGEXP_RANGE_QUANTIFIER_STATE_START,
155 PM_REGEXP_RANGE_QUANTIFIER_STATE_MINIMUM,
156 PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM,
157 PM_REGEXP_RANGE_QUANTIFIER_STATE_COMMA
158 } state = PM_REGEXP_RANGE_QUANTIFIER_STATE_START;
159
160 while (1) {
161 if (parser->cursor >= parser->end) {
162 parser->cursor = savepoint;
163 return true;
164 }
165
166 switch (state) {
167 case PM_REGEXP_RANGE_QUANTIFIER_STATE_START:
168 switch (*parser->cursor) {
169 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
170 parser->cursor++;
171 state = PM_REGEXP_RANGE_QUANTIFIER_STATE_MINIMUM;
172 break;
173 case ',':
174 parser->cursor++;
175 state = PM_REGEXP_RANGE_QUANTIFIER_STATE_COMMA;
176 break;
177 default:
178 parser->cursor = savepoint;
179 return true;
180 }
181 break;
182 case PM_REGEXP_RANGE_QUANTIFIER_STATE_MINIMUM:
183 switch (*parser->cursor) {
184 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
185 parser->cursor++;
186 break;
187 case ',':
188 parser->cursor++;
189 state = PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM;
190 break;
191 case '}':
192 parser->cursor++;
193 return true;
194 default:
195 parser->cursor = savepoint;
196 return true;
197 }
198 break;
199 case PM_REGEXP_RANGE_QUANTIFIER_STATE_COMMA:
200 switch (*parser->cursor) {
201 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
202 parser->cursor++;
203 state = PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM;
204 break;
205 default:
206 parser->cursor = savepoint;
207 return true;
208 }
209 break;
210 case PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM:
211 switch (*parser->cursor) {
212 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
213 parser->cursor++;
214 break;
215 case '}':
216 parser->cursor++;
217 return true;
218 default:
219 parser->cursor = savepoint;
220 return true;
221 }
222 break;
223 }
224 }
225
226 return true;
227}
228
237static bool
238pm_regexp_parse_quantifier(pm_regexp_parser_t *parser) {
239 while (!pm_regexp_char_is_eof(parser)) {
240 switch (*parser->cursor) {
241 case '*':
242 case '+':
243 case '?':
244 parser->cursor++;
245 break;
246 case '{':
247 parser->cursor++;
248 if (!pm_regexp_parse_range_quantifier(parser)) return false;
249 break;
250 default:
251 // In this case there is no quantifier.
252 return true;
253 }
254 }
255
256 return true;
257}
258
263static bool
264pm_regexp_parse_posix_class(pm_regexp_parser_t *parser) {
265 if (!pm_regexp_char_expect(parser, ':')) {
266 return false;
267 }
268
269 pm_regexp_char_accept(parser, '^');
270
271 return (
272 pm_regexp_char_find(parser, ':') &&
273 pm_regexp_char_expect(parser, ']') &&
274 pm_regexp_char_expect(parser, ']')
275 );
276}
277
278// Forward declaration because character sets can be nested.
279static bool
280pm_regexp_parse_lbracket(pm_regexp_parser_t *parser, uint16_t depth);
281
286static bool
287pm_regexp_parse_character_set(pm_regexp_parser_t *parser, uint16_t depth) {
288 pm_regexp_char_accept(parser, '^');
289
290 while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ']') {
291 switch (*parser->cursor++) {
292 case '[':
293 pm_regexp_parse_lbracket(parser, (uint16_t) (depth + 1));
294 break;
295 case '\\':
296 if (!pm_regexp_char_is_eof(parser)) {
297 parser->cursor++;
298 }
299 break;
300 default:
301 // do nothing, we've already advanced the cursor
302 break;
303 }
304 }
305
306 return pm_regexp_char_expect(parser, ']');
307}
308
312static bool
313pm_regexp_parse_lbracket(pm_regexp_parser_t *parser, uint16_t depth) {
314 if (depth >= PM_REGEXP_PARSE_DEPTH_MAX) {
315 pm_regexp_parse_error(parser, parser->start, parser->end, "parse depth limit over");
316 return false;
317 }
318
319 if ((parser->cursor < parser->end) && parser->cursor[0] == ']') {
320 parser->cursor++;
321 pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "empty char-class");
322 return true;
323 }
324
325 const uint8_t *reset = parser->cursor;
326
327 if ((parser->cursor + 2 < parser->end) && parser->cursor[0] == '[' && parser->cursor[1] == ':') {
328 parser->cursor++;
329 if (pm_regexp_parse_posix_class(parser)) return true;
330
331 parser->cursor = reset;
332 }
333
334 return pm_regexp_parse_character_set(parser, depth);
335}
336
337// Forward declaration here since parsing groups needs to go back up the grammar
338// to parse expressions within them.
339static bool
340pm_regexp_parse_expression(pm_regexp_parser_t *parser, uint16_t depth);
341
346typedef enum {
347 PM_REGEXP_OPTION_STATE_INVALID,
348 PM_REGEXP_OPTION_STATE_TOGGLEABLE,
349 PM_REGEXP_OPTION_STATE_ADDABLE,
350 PM_REGEXP_OPTION_STATE_ADDED,
351 PM_REGEXP_OPTION_STATE_REMOVED
352} pm_regexp_option_state_t;
353
354// These are the options that are configurable on the regular expression (or
355// from within a group).
356
357#define PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM 'a'
358#define PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM 'x'
359#define PRISM_REGEXP_OPTION_STATE_SLOTS (PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM + 1)
360
364typedef struct {
366 uint8_t values[PRISM_REGEXP_OPTION_STATE_SLOTS];
368
372static void
373pm_regexp_options_init(pm_regexp_options_t *options) {
374 memset(options, PM_REGEXP_OPTION_STATE_INVALID, sizeof(uint8_t) * PRISM_REGEXP_OPTION_STATE_SLOTS);
375 options->values['i' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_TOGGLEABLE;
376 options->values['m' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_TOGGLEABLE;
377 options->values['x' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_TOGGLEABLE;
378 options->values['d' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_ADDABLE;
379 options->values['a' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_ADDABLE;
380 options->values['u' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_ADDABLE;
381}
382
387static bool
388pm_regexp_options_add(pm_regexp_options_t *options, uint8_t key) {
389 if (key >= PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM && key <= PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM) {
390 key = (uint8_t) (key - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM);
391
392 switch (options->values[key]) {
393 case PM_REGEXP_OPTION_STATE_INVALID:
394 case PM_REGEXP_OPTION_STATE_REMOVED:
395 return false;
396 case PM_REGEXP_OPTION_STATE_TOGGLEABLE:
397 case PM_REGEXP_OPTION_STATE_ADDABLE:
398 options->values[key] = PM_REGEXP_OPTION_STATE_ADDED;
399 return true;
400 case PM_REGEXP_OPTION_STATE_ADDED:
401 return true;
402 }
403 }
404
405 return false;
406}
407
412static bool
413pm_regexp_options_remove(pm_regexp_options_t *options, uint8_t key) {
414 if (key >= PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM && key <= PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM) {
415 key = (uint8_t) (key - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM);
416
417 switch (options->values[key]) {
418 case PM_REGEXP_OPTION_STATE_INVALID:
419 case PM_REGEXP_OPTION_STATE_ADDABLE:
420 return false;
421 case PM_REGEXP_OPTION_STATE_TOGGLEABLE:
422 case PM_REGEXP_OPTION_STATE_ADDED:
423 case PM_REGEXP_OPTION_STATE_REMOVED:
424 options->values[key] = PM_REGEXP_OPTION_STATE_REMOVED;
425 return true;
426 }
427 }
428
429 return false;
430}
431
435static uint8_t
436pm_regexp_options_state(pm_regexp_options_t *options, uint8_t key) {
437 if (key >= PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM && key <= PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM) {
438 key = (uint8_t) (key - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM);
439 return options->values[key];
440 }
441
442 return false;
443}
444
466static bool
467pm_regexp_parse_group(pm_regexp_parser_t *parser, uint16_t depth) {
468 const uint8_t *group_start = parser->cursor;
469
470 pm_regexp_options_t options;
471 pm_regexp_options_init(&options);
472
473 // First, parse any options for the group.
474 if (pm_regexp_char_accept(parser, '?')) {
475 if (pm_regexp_char_is_eof(parser)) {
476 pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern in group");
477 return false;
478 }
479
480 switch (*parser->cursor) {
481 case '#': { // inline comments
482 parser->cursor++;
483 if (pm_regexp_char_is_eof(parser)) {
484 pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern in group");
485 return false;
486 }
487
488 if (parser->encoding_changed && parser->encoding->multibyte) {
489 bool escaped = false;
490
491 // Here we're going to take a slow path and iterate through
492 // each multibyte character to find the close paren. We do
493 // this because \ can be a trailing byte in some encodings.
494 while (parser->cursor < parser->end) {
495 if (!escaped && *parser->cursor == ')') {
496 parser->cursor++;
497 return true;
498 }
499
500 size_t width = parser->encoding->char_width(parser->cursor, (ptrdiff_t) (parser->end - parser->cursor));
501 if (width == 0) return false;
502
503 escaped = (width == 1) && (*parser->cursor == '\\');
504 parser->cursor += width;
505 }
506
507 return false;
508 } else {
509 // Here we can take the fast path and use memchr to find the
510 // next ) because we are safe checking backward for \ since
511 // it cannot be a trailing character.
512 bool found = pm_regexp_char_find(parser, ')');
513
514 while (found && (parser->start <= parser->cursor - 2) && (*(parser->cursor - 2) == '\\')) {
515 found = pm_regexp_char_find(parser, ')');
516 }
517
518 return found;
519 }
520 }
521 case ':': // non-capturing group
522 case '=': // positive lookahead
523 case '!': // negative lookahead
524 case '>': // atomic group
525 case '~': // absence operator
526 parser->cursor++;
527 break;
528 case '<':
529 parser->cursor++;
530 if (pm_regexp_char_is_eof(parser)) {
531 pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern with unmatched parenthesis");
532 return false;
533 }
534
535 switch (*parser->cursor) {
536 case '=': // positive lookbehind
537 case '!': // negative lookbehind
538 parser->cursor++;
539 break;
540 default: { // named capture group
541 const uint8_t *start = parser->cursor;
542 if (!pm_regexp_char_find(parser, '>')) {
543 return false;
544 }
545
546 if (parser->cursor - start == 1) {
547 pm_regexp_parse_error(parser, start, parser->cursor, "group name is empty");
548 }
549
550 if (parser->name_callback != NULL) {
551 pm_regexp_parser_named_capture(parser, start, parser->cursor - 1);
552 }
553
554 break;
555 }
556 }
557 break;
558 case '\'': { // named capture group
559 const uint8_t *start = ++parser->cursor;
560 if (!pm_regexp_char_find(parser, '\'')) {
561 return false;
562 }
563
564 if (parser->name_callback != NULL) {
565 pm_regexp_parser_named_capture(parser, start, parser->cursor - 1);
566 }
567
568 break;
569 }
570 case '(': // conditional expression
571 if (!pm_regexp_char_find(parser, ')')) {
572 return false;
573 }
574 break;
575 case 'i': case 'm': case 'x': case 'd': case 'a': case 'u': // options
576 while (!pm_regexp_char_is_eof(parser) && *parser->cursor != '-' && *parser->cursor != ':' && *parser->cursor != ')') {
577 if (!pm_regexp_options_add(&options, *parser->cursor)) {
578 return false;
579 }
580 parser->cursor++;
581 }
582
583 if (pm_regexp_char_is_eof(parser)) {
584 return false;
585 }
586
587 // If we are at the end of the group of options and there is no
588 // subexpression, then we are going to be setting the options
589 // for the parent group. In this case we are safe to return now.
590 if (*parser->cursor == ')') {
591 if (pm_regexp_options_state(&options, 'x') == PM_REGEXP_OPTION_STATE_ADDED) {
592 parser->extended_mode = true;
593 }
594
595 parser->cursor++;
596 return true;
597 }
598
599 // If we hit a -, then we're done parsing options.
600 if (*parser->cursor != '-') break;
601
603 case '-':
604 parser->cursor++;
605 while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ':' && *parser->cursor != ')') {
606 if (!pm_regexp_options_remove(&options, *parser->cursor)) {
607 return false;
608 }
609 parser->cursor++;
610 }
611
612 if (pm_regexp_char_is_eof(parser)) {
613 return false;
614 }
615
616 // If we are at the end of the group of options and there is no
617 // subexpression, then we are going to be setting the options
618 // for the parent group. In this case we are safe to return now.
619 if (*parser->cursor == ')') {
620 switch (pm_regexp_options_state(&options, 'x')) {
621 case PM_REGEXP_OPTION_STATE_ADDED:
622 parser->extended_mode = true;
623 break;
624 case PM_REGEXP_OPTION_STATE_REMOVED:
625 parser->extended_mode = false;
626 break;
627 }
628
629 parser->cursor++;
630 return true;
631 }
632
633 break;
634 default:
635 parser->cursor++;
636 pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "undefined group option");
637 break;
638 }
639 }
640
641 bool extended_mode = parser->extended_mode;
642 switch (pm_regexp_options_state(&options, 'x')) {
643 case PM_REGEXP_OPTION_STATE_ADDED:
644 parser->extended_mode = true;
645 break;
646 case PM_REGEXP_OPTION_STATE_REMOVED:
647 parser->extended_mode = false;
648 break;
649 }
650
651 // Now, parse the expressions within this group.
652 while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ')') {
653 if (!pm_regexp_parse_expression(parser, (uint16_t) (depth + 1))) {
654 parser->extended_mode = extended_mode;
655 return false;
656 }
657 pm_regexp_char_accept(parser, '|');
658 }
659
660 // Finally, make sure we have a closing parenthesis.
661 parser->extended_mode = extended_mode;
662 if (pm_regexp_char_expect(parser, ')')) return true;
663
664 pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern with unmatched parenthesis");
665 return false;
666}
667
680static bool
681pm_regexp_parse_item(pm_regexp_parser_t *parser, uint16_t depth) {
682 switch (*parser->cursor) {
683 case '^':
684 case '$':
685 parser->cursor++;
686 return pm_regexp_parse_quantifier(parser);
687 case '\\':
688 parser->cursor++;
689 if (!pm_regexp_char_is_eof(parser)) {
690 parser->cursor++;
691 }
692 return pm_regexp_parse_quantifier(parser);
693 case '(':
694 parser->cursor++;
695 return pm_regexp_parse_group(parser, depth) && pm_regexp_parse_quantifier(parser);
696 case '[':
697 parser->cursor++;
698 return pm_regexp_parse_lbracket(parser, depth) && pm_regexp_parse_quantifier(parser);
699 case '*':
700 case '?':
701 case '+':
702 parser->cursor++;
703 pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "target of repeat operator is not specified");
704 return true;
705 case ')':
706 parser->cursor++;
707 pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "unmatched close parenthesis");
708 return true;
709 case '#':
710 if (parser->extended_mode) {
711 if (!pm_regexp_char_find(parser, '\n')) parser->cursor = parser->end;
712 return true;
713 }
715 default: {
716 size_t width;
717 if (!parser->encoding_changed) {
718 width = pm_encoding_utf_8_char_width(parser->cursor, (ptrdiff_t) (parser->end - parser->cursor));
719 } else {
720 width = parser->encoding->char_width(parser->cursor, (ptrdiff_t) (parser->end - parser->cursor));
721 }
722
723 if (width == 0) return false; // TODO: add appropriate error
724 parser->cursor += width;
725
726 return pm_regexp_parse_quantifier(parser);
727 }
728 }
729}
730
735static bool
736pm_regexp_parse_expression(pm_regexp_parser_t *parser, uint16_t depth) {
737 if (depth >= PM_REGEXP_PARSE_DEPTH_MAX) {
738 pm_regexp_parse_error(parser, parser->start, parser->end, "parse depth limit over");
739 return false;
740 }
741
742 if (!pm_regexp_parse_item(parser, depth)) {
743 return false;
744 }
745
746 while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ')' && *parser->cursor != '|') {
747 if (!pm_regexp_parse_item(parser, depth)) {
748 return false;
749 }
750 }
751
752 return true;
753}
754
761static bool
762pm_regexp_parse_pattern(pm_regexp_parser_t *parser) {
763 do {
764 if (pm_regexp_char_is_eof(parser)) return true;
765 if (!pm_regexp_parse_expression(parser, 0)) return false;
766 } while (pm_regexp_char_accept(parser, '|'));
767
768 return pm_regexp_char_is_eof(parser);
769}
770
776pm_regexp_parse(pm_parser_t *parser, const uint8_t *source, size_t size, bool extended_mode, pm_regexp_name_callback_t name_callback, void *name_data, pm_regexp_error_callback_t error_callback, void *error_data) {
777 pm_regexp_parse_pattern(&(pm_regexp_parser_t) {
778 .parser = parser,
779 .start = source,
780 .cursor = source,
781 .end = source + size,
782 .extended_mode = extended_mode,
783 .encoding_changed = parser->encoding_changed,
784 .encoding = parser->encoding,
785 .name_callback = name_callback,
786 .name_data = name_data,
787 .error_callback = error_callback,
788 .error_data = error_data
789 });
790}
#define PRISM_FALLTHROUGH
We use -Wimplicit-fallthrough to guard potentially unintended fall-through between cases of a switch.
Definition defines.h:253
#define PRISM_EXPORTED_FUNCTION
By default, we compile with -fvisibility=hidden.
Definition defines.h:53
A regular expression parser.
void(* pm_regexp_error_callback_t)(const uint8_t *start, const uint8_t *end, const char *message, void *data)
This callback is called when a parse error is found.
Definition regexp.h:27
void(* pm_regexp_name_callback_t)(const pm_string_t *name, void *data)
This callback is called when a named capture group is found.
Definition regexp.h:22
This struct defines the functions necessary to implement the encoding interface so we can determine h...
Definition encoding.h:23
size_t(* char_width)(const uint8_t *b, ptrdiff_t n)
Return the number of bytes that the next character takes if it is valid in the encoding.
Definition encoding.h:29
bool multibyte
Return true if the encoding is a multibyte encoding.
Definition encoding.h:61
This struct represents the overall parser.
Definition parser.h:640
const pm_encoding_t * encoding
The encoding functions for the current file is attached to the parser as it's parsing so that it can ...
Definition parser.h:755
bool encoding_changed
Whether or not the encoding has been changed by a magic comment.
Definition parser.h:903
const uint8_t * start
The pointer to the start of the source.
Definition parser.h:691
This is the set of options that are configurable on the regular expression.
Definition regexp.c:364
uint8_t values[PRISM_REGEXP_OPTION_STATE_SLOTS]
The current state of each option.
Definition regexp.c:366
This is the parser that is going to handle parsing regular expressions.
Definition regexp.c:8
const uint8_t * cursor
A pointer to the current position in the source.
Definition regexp.c:16
pm_regexp_error_callback_t error_callback
The callback to call when a parse error is found.
Definition regexp.c:40
const uint8_t * start
A pointer to the start of the source that we are parsing.
Definition regexp.c:13
const uint8_t * end
A pointer to the end of the source that we are parsing.
Definition regexp.c:19
void * name_data
The data to pass to the name callback.
Definition regexp.c:37
bool extended_mode
Whether or not the regular expression currently being parsed is in extended mode, wherein whitespace ...
Definition regexp.c:25
pm_parser_t * parser
The parser that is currently being used.
Definition regexp.c:10
const pm_encoding_t * encoding
The encoding of the source.
Definition regexp.c:31
void * error_data
The data to pass to the error callback.
Definition regexp.c:43
pm_regexp_name_callback_t name_callback
The callback to call when a named capture group is found.
Definition regexp.c:34
bool encoding_changed
Whether the encoding has changed from the default.
Definition regexp.c:28
A generic string type that can have various ownership semantics.
Definition pm_string.h:33