re2 2.9.0 → 2.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/re2/re2.cc CHANGED
@@ -8,7 +8,7 @@
8
8
  * Released under the BSD Licence, please see LICENSE.txt
9
9
  */
10
10
 
11
- #include <stdint.h>
11
+ #include <cstdint>
12
12
 
13
13
  #include <map>
14
14
  #include <sstream>
@@ -19,6 +19,7 @@
19
19
  #include <re2/set.h>
20
20
  #include <ruby.h>
21
21
  #include <ruby/encoding.h>
22
+ #include <ruby/thread.h>
22
23
 
23
24
  #define BOOL2RUBY(v) (v ? Qtrue : Qfalse)
24
25
 
@@ -43,6 +44,132 @@ typedef struct {
43
44
  RE2::Set *set;
44
45
  } re2_set;
45
46
 
47
+ struct nogvl_match_arg {
48
+ const RE2 *pattern;
49
+ re2::StringPiece text;
50
+ size_t startpos;
51
+ size_t endpos;
52
+ RE2::Anchor anchor;
53
+ re2::StringPiece *matches;
54
+ int n;
55
+ bool matched;
56
+ };
57
+
58
+ static void *nogvl_match(void *ptr) {
59
+ auto *arg = static_cast<nogvl_match_arg *>(ptr);
60
+ #ifdef HAVE_ENDPOS_ARGUMENT
61
+ arg->matched = arg->pattern->Match(
62
+ arg->text, arg->startpos, arg->endpos,
63
+ arg->anchor, arg->matches, arg->n);
64
+ #else
65
+ arg->matched = arg->pattern->Match(
66
+ arg->text, arg->startpos,
67
+ arg->anchor, arg->matches, arg->n);
68
+ #endif
69
+ return nullptr;
70
+ }
71
+
72
+ static bool re2_match_without_gvl(
73
+ const RE2 *pattern, VALUE text, size_t startpos, size_t endpos,
74
+ RE2::Anchor anchor, re2::StringPiece *matches, int n) {
75
+ nogvl_match_arg arg;
76
+ arg.pattern = pattern;
77
+ arg.text = re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text));
78
+ arg.startpos = startpos;
79
+ arg.endpos = endpos;
80
+ arg.anchor = anchor;
81
+ arg.matches = matches;
82
+ arg.n = n;
83
+ arg.matched = false;
84
+
85
+ /* Abseil's synchronization primitives (SRWLOCK, SleepConditionVariableSRW)
86
+ * are incompatible with Ruby's Win32 Mutex-based GVL, causing
87
+ * WAIT_ABANDONED crashes when multiple threads match concurrently.
88
+ */
89
+ #ifdef _WIN32
90
+ nogvl_match(&arg);
91
+ #else
92
+ /* No unblocking function is needed: RE2 matching is CPU-bound computation,
93
+ * not a blocking system call, so a signal cannot safely interrupt it.
94
+ */
95
+ rb_thread_call_without_gvl(nogvl_match, &arg, NULL, NULL);
96
+ #endif
97
+
98
+ return arg.matched;
99
+ }
100
+
101
+ struct nogvl_set_match_arg {
102
+ const RE2::Set *set;
103
+ re2::StringPiece text;
104
+ std::vector<int> *v;
105
+ #ifdef HAVE_ERROR_INFO_ARGUMENT
106
+ RE2::Set::ErrorInfo *error_info;
107
+ #endif
108
+ bool matched;
109
+ };
110
+
111
+ static void *nogvl_set_match(void *ptr) {
112
+ auto *arg = static_cast<nogvl_set_match_arg *>(ptr);
113
+ #ifdef HAVE_ERROR_INFO_ARGUMENT
114
+ if (arg->error_info) {
115
+ arg->matched = arg->set->Match(arg->text, arg->v, arg->error_info);
116
+ } else {
117
+ arg->matched = arg->set->Match(arg->text, arg->v);
118
+ }
119
+ #else
120
+ arg->matched = arg->set->Match(arg->text, arg->v);
121
+ #endif
122
+ return nullptr;
123
+ }
124
+
125
+ struct nogvl_replace_arg {
126
+ std::string *str;
127
+ const RE2 *pattern;
128
+ re2::StringPiece string_pattern;
129
+ re2::StringPiece rewrite;
130
+ };
131
+
132
+ static void *nogvl_replace(void *ptr) {
133
+ auto *arg = static_cast<nogvl_replace_arg *>(ptr);
134
+ if (arg->pattern) {
135
+ RE2::Replace(arg->str, *arg->pattern, arg->rewrite);
136
+ } else {
137
+ RE2::Replace(arg->str, arg->string_pattern, arg->rewrite);
138
+ }
139
+ return nullptr;
140
+ }
141
+
142
+ static void *nogvl_global_replace(void *ptr) {
143
+ auto *arg = static_cast<nogvl_replace_arg *>(ptr);
144
+ if (arg->pattern) {
145
+ RE2::GlobalReplace(arg->str, *arg->pattern, arg->rewrite);
146
+ } else {
147
+ RE2::GlobalReplace(arg->str, arg->string_pattern, arg->rewrite);
148
+ }
149
+ return nullptr;
150
+ }
151
+
152
+ struct nogvl_extract_arg {
153
+ re2::StringPiece text;
154
+ const RE2 *pattern;
155
+ re2::StringPiece string_pattern;
156
+ re2::StringPiece rewrite;
157
+ std::string *out;
158
+ bool extracted;
159
+ };
160
+
161
+ static void *nogvl_extract(void *ptr) {
162
+ auto *arg = static_cast<nogvl_extract_arg *>(ptr);
163
+ if (arg->pattern) {
164
+ arg->extracted = RE2::Extract(arg->text, *arg->pattern,
165
+ arg->rewrite, arg->out);
166
+ } else {
167
+ arg->extracted = RE2::Extract(arg->text, RE2(arg->string_pattern),
168
+ arg->rewrite, arg->out);
169
+ }
170
+ return nullptr;
171
+ }
172
+
46
173
  VALUE re2_mRE2, re2_cRegexp, re2_cMatchData, re2_cScanner, re2_cSet,
47
174
  re2_eSetMatchError, re2_eSetUnsupportedError, re2_eRegexpUnsupportedError;
48
175
 
@@ -51,7 +178,7 @@ static ID id_utf8, id_posix_syntax, id_longest_match, id_log_errors,
51
178
  id_max_mem, id_literal, id_never_nl, id_case_sensitive,
52
179
  id_perl_classes, id_word_boundary, id_one_line, id_unanchored,
53
180
  id_anchor, id_anchor_start, id_anchor_both, id_exception,
54
- id_submatches, id_startpos, id_endpos;
181
+ id_submatches, id_startpos, id_endpos, id_symbolize_names;
55
182
 
56
183
  inline VALUE encoded_str_new(const char *str, long length, RE2::Options::Encoding encoding) {
57
184
  if (encoding == RE2::Options::EncodingUTF8) {
@@ -125,30 +252,23 @@ static void parse_re2_options(RE2::Options* re2_options, const VALUE options) {
125
252
  }
126
253
  }
127
254
 
128
- /* For compatibility with Ruby < 2.7 */
129
- #ifdef HAVE_RB_GC_MARK_MOVABLE
130
- #define re2_compact_callback(x) (x),
131
- #else
132
- #define rb_gc_mark_movable(x) rb_gc_mark(x)
133
- #define re2_compact_callback(x)
134
- #endif
135
-
136
255
  static void re2_matchdata_mark(void *ptr) {
137
- re2_matchdata *m = reinterpret_cast<re2_matchdata *>(ptr);
256
+ re2_matchdata *m = static_cast<re2_matchdata *>(ptr);
138
257
  rb_gc_mark_movable(m->regexp);
139
- rb_gc_mark_movable(m->text);
258
+
259
+ /* Text must not be movable because StringPiece matches hold pointers into
260
+ * its underlying buffer; moving the string would invalidate them.
261
+ */
262
+ rb_gc_mark(m->text);
140
263
  }
141
264
 
142
- #ifdef HAVE_RB_GC_MARK_MOVABLE
143
265
  static void re2_matchdata_compact(void *ptr) {
144
- re2_matchdata *m = reinterpret_cast<re2_matchdata *>(ptr);
266
+ re2_matchdata *m = static_cast<re2_matchdata *>(ptr);
145
267
  m->regexp = rb_gc_location(m->regexp);
146
- m->text = rb_gc_location(m->text);
147
268
  }
148
- #endif
149
269
 
150
270
  static void re2_matchdata_free(void *ptr) {
151
- re2_matchdata *m = reinterpret_cast<re2_matchdata *>(ptr);
271
+ re2_matchdata *m = static_cast<re2_matchdata *>(ptr);
152
272
  if (m->matches) {
153
273
  delete[] m->matches;
154
274
  }
@@ -156,7 +276,7 @@ static void re2_matchdata_free(void *ptr) {
156
276
  }
157
277
 
158
278
  static size_t re2_matchdata_memsize(const void *ptr) {
159
- const re2_matchdata *m = reinterpret_cast<const re2_matchdata *>(ptr);
279
+ const re2_matchdata *m = static_cast<const re2_matchdata *>(ptr);
160
280
  size_t size = sizeof(*m);
161
281
  if (m->matches) {
162
282
  size += sizeof(*m->matches) * m->number_of_matches;
@@ -171,7 +291,7 @@ static const rb_data_type_t re2_matchdata_data_type = {
171
291
  re2_matchdata_mark,
172
292
  re2_matchdata_free,
173
293
  re2_matchdata_memsize,
174
- re2_compact_callback(re2_matchdata_compact)
294
+ re2_matchdata_compact
175
295
  },
176
296
  0,
177
297
  0,
@@ -181,21 +301,22 @@ static const rb_data_type_t re2_matchdata_data_type = {
181
301
  };
182
302
 
183
303
  static void re2_scanner_mark(void *ptr) {
184
- re2_scanner *s = reinterpret_cast<re2_scanner *>(ptr);
304
+ re2_scanner *s = static_cast<re2_scanner *>(ptr);
185
305
  rb_gc_mark_movable(s->regexp);
186
- rb_gc_mark_movable(s->text);
306
+
307
+ /* Text must not be movable because the StringPiece input holds a pointer
308
+ * into its underlying buffer; moving the string would invalidate it.
309
+ */
310
+ rb_gc_mark(s->text);
187
311
  }
188
312
 
189
- #ifdef HAVE_RB_GC_MARK_MOVABLE
190
313
  static void re2_scanner_compact(void *ptr) {
191
- re2_scanner *s = reinterpret_cast<re2_scanner *>(ptr);
314
+ re2_scanner *s = static_cast<re2_scanner *>(ptr);
192
315
  s->regexp = rb_gc_location(s->regexp);
193
- s->text = rb_gc_location(s->text);
194
316
  }
195
- #endif
196
317
 
197
318
  static void re2_scanner_free(void *ptr) {
198
- re2_scanner *s = reinterpret_cast<re2_scanner *>(ptr);
319
+ re2_scanner *s = static_cast<re2_scanner *>(ptr);
199
320
  if (s->input) {
200
321
  delete s->input;
201
322
  }
@@ -203,7 +324,7 @@ static void re2_scanner_free(void *ptr) {
203
324
  }
204
325
 
205
326
  static size_t re2_scanner_memsize(const void *ptr) {
206
- const re2_scanner *s = reinterpret_cast<const re2_scanner *>(ptr);
327
+ const re2_scanner *s = static_cast<const re2_scanner *>(ptr);
207
328
  size_t size = sizeof(*s);
208
329
  if (s->input) {
209
330
  size += sizeof(*s->input);
@@ -218,7 +339,7 @@ static const rb_data_type_t re2_scanner_data_type = {
218
339
  re2_scanner_mark,
219
340
  re2_scanner_free,
220
341
  re2_scanner_memsize,
221
- re2_compact_callback(re2_scanner_compact)
342
+ re2_scanner_compact
222
343
  },
223
344
  0,
224
345
  0,
@@ -228,7 +349,7 @@ static const rb_data_type_t re2_scanner_data_type = {
228
349
  };
229
350
 
230
351
  static void re2_regexp_free(void *ptr) {
231
- re2_pattern *p = reinterpret_cast<re2_pattern *>(ptr);
352
+ re2_pattern *p = static_cast<re2_pattern *>(ptr);
232
353
  if (p->pattern) {
233
354
  delete p->pattern;
234
355
  }
@@ -236,7 +357,7 @@ static void re2_regexp_free(void *ptr) {
236
357
  }
237
358
 
238
359
  static size_t re2_regexp_memsize(const void *ptr) {
239
- const re2_pattern *p = reinterpret_cast<const re2_pattern *>(ptr);
360
+ const re2_pattern *p = static_cast<const re2_pattern *>(ptr);
240
361
  size_t size = sizeof(*p);
241
362
  if (p->pattern) {
242
363
  size += sizeof(*p->pattern);
@@ -256,9 +377,64 @@ static const rb_data_type_t re2_regexp_data_type = {
256
377
  0,
257
378
  // IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
258
379
  // macro to update VALUE references, as to trigger write barriers.
259
- RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
380
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE
260
381
  };
261
382
 
383
+ static re2_pattern *unwrap_re2_regexp(VALUE self) {
384
+ re2_pattern *p;
385
+ TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
386
+ if (!p->pattern) {
387
+ rb_raise(rb_eTypeError, "uninitialized RE2::Regexp");
388
+ }
389
+ return p;
390
+ }
391
+
392
+ static re2_matchdata *unwrap_re2_matchdata(VALUE self) {
393
+ re2_matchdata *m;
394
+ TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
395
+ if (!RTEST(m->regexp)) {
396
+ rb_raise(rb_eTypeError, "uninitialized RE2::MatchData");
397
+ }
398
+ return m;
399
+ }
400
+
401
+ static re2_scanner *unwrap_re2_scanner(VALUE self) {
402
+ re2_scanner *c;
403
+ TypedData_Get_Struct(self, re2_scanner, &re2_scanner_data_type, c);
404
+ if (!RTEST(c->regexp)) {
405
+ rb_raise(rb_eTypeError, "uninitialized RE2::Scanner");
406
+ }
407
+ return c;
408
+ }
409
+
410
+ /*
411
+ * Returns an array of names of all named capturing groups. Names are returned
412
+ * in alphabetical order rather than definition order, as RE2 stores named
413
+ * groups internally in a sorted map.
414
+ *
415
+ * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
416
+ * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
417
+ * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
418
+ *
419
+ * @return [Array<String>] an array of names of named capturing groups
420
+ * @example
421
+ * RE2::Regexp.new('(?P<a>\d+) (?P<b>\w+)').names #=> ["a", "b"]
422
+ */
423
+ static VALUE re2_regexp_names(const VALUE self) {
424
+ re2_pattern *p = unwrap_re2_regexp(self);
425
+
426
+ const auto& groups = p->pattern->NamedCapturingGroups();
427
+ VALUE names = rb_ary_new2(groups.size());
428
+
429
+ for (const auto& group : groups) {
430
+ rb_ary_push(names,
431
+ encoded_str_new(group.first.data(), group.first.size(),
432
+ p->pattern->options().encoding()));
433
+ }
434
+
435
+ return names;
436
+ }
437
+
262
438
  static VALUE re2_matchdata_allocate(VALUE klass) {
263
439
  re2_matchdata *m;
264
440
 
@@ -283,24 +459,24 @@ static VALUE re2_scanner_allocate(VALUE klass) {
283
459
  * m.string #=> "bob 123"
284
460
  */
285
461
  static VALUE re2_matchdata_string(const VALUE self) {
286
- re2_matchdata *m;
287
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
462
+ re2_matchdata *m = unwrap_re2_matchdata(self);
288
463
 
289
464
  return m->text;
290
465
  }
291
466
 
292
467
  /*
293
- * Returns the text supplied when incrementally matching with
468
+ * Returns a frozen copy of the text supplied when incrementally matching with
294
469
  * {RE2::Regexp#scan}.
295
470
  *
296
- * @return [String] the original string passed to {RE2::Regexp#scan}
471
+ * If the text was already a frozen string, returns the original.
472
+ *
473
+ * @return [String] a frozen string with the text passed to {RE2::Regexp#scan}
297
474
  * @example
298
475
  * c = RE2::Regexp.new('(\d+)').scan("foo")
299
476
  * c.string #=> "foo"
300
477
  */
301
478
  static VALUE re2_scanner_string(const VALUE self) {
302
- re2_scanner *c;
303
- TypedData_Get_Struct(self, re2_scanner, &re2_scanner_data_type, c);
479
+ re2_scanner *c = unwrap_re2_scanner(self);
304
480
 
305
481
  return c->text;
306
482
  }
@@ -314,8 +490,7 @@ static VALUE re2_scanner_string(const VALUE self) {
314
490
  * c.eof? #=> true
315
491
  */
316
492
  static VALUE re2_scanner_eof(const VALUE self) {
317
- re2_scanner *c;
318
- TypedData_Get_Struct(self, re2_scanner, &re2_scanner_data_type, c);
493
+ re2_scanner *c = unwrap_re2_scanner(self);
319
494
 
320
495
  return BOOL2RUBY(c->eof);
321
496
  }
@@ -332,17 +507,50 @@ static VALUE re2_scanner_eof(const VALUE self) {
332
507
  * e.scan #=> ["1"]
333
508
  */
334
509
  static VALUE re2_scanner_rewind(VALUE self) {
335
- re2_scanner *c;
336
- TypedData_Get_Struct(self, re2_scanner, &re2_scanner_data_type, c);
510
+ re2_scanner *c = unwrap_re2_scanner(self);
337
511
 
338
512
  delete c->input;
339
513
  c->input = new(std::nothrow) re2::StringPiece(
340
514
  RSTRING_PTR(c->text), RSTRING_LEN(c->text));
515
+ if (c->input == nullptr) {
516
+ rb_raise(rb_eNoMemError,
517
+ "not enough memory to allocate StringPiece for input");
518
+ }
519
+
341
520
  c->eof = false;
342
521
 
343
522
  return self;
344
523
  }
345
524
 
525
+ static VALUE re2_scanner_initialize_copy(VALUE self, VALUE other) {
526
+ re2_scanner *self_c;
527
+ re2_scanner *other_c = unwrap_re2_scanner(other);
528
+
529
+ TypedData_Get_Struct(self, re2_scanner, &re2_scanner_data_type, self_c);
530
+
531
+ if (self_c->input) {
532
+ delete self_c->input;
533
+ self_c->input = nullptr;
534
+ }
535
+
536
+ RB_OBJ_WRITE(self, &self_c->regexp, other_c->regexp);
537
+ RB_OBJ_WRITE(self, &self_c->text, other_c->text);
538
+ self_c->number_of_capturing_groups = other_c->number_of_capturing_groups;
539
+ self_c->eof = other_c->eof;
540
+
541
+ if (other_c->input) {
542
+ self_c->input = new(std::nothrow) re2::StringPiece(*other_c->input);
543
+ if (self_c->input == nullptr) {
544
+ rb_raise(rb_eNoMemError,
545
+ "not enough memory to allocate StringPiece for input");
546
+ }
547
+ } else {
548
+ self_c->input = nullptr;
549
+ }
550
+
551
+ return self;
552
+ }
553
+
346
554
  /*
347
555
  * Scan the given text incrementally for matches using
348
556
  * {https://github.com/google/re2/blob/bc0faab533e2b27b85b8ad312abf061e33ed6b5d/re2/re2.h#L447-L463
@@ -363,11 +571,8 @@ static VALUE re2_scanner_rewind(VALUE self) {
363
571
  * s.scan #=> ["bar"]
364
572
  */
365
573
  static VALUE re2_scanner_scan(VALUE self) {
366
- re2_pattern *p;
367
- re2_scanner *c;
368
-
369
- TypedData_Get_Struct(self, re2_scanner, &re2_scanner_data_type, c);
370
- TypedData_Get_Struct(c->regexp, re2_pattern, &re2_regexp_data_type, p);
574
+ re2_scanner *c = unwrap_re2_scanner(self);
575
+ re2_pattern *p = unwrap_re2_regexp(c->regexp);
371
576
 
372
577
  std::vector<RE2::Arg> argv(c->number_of_capturing_groups);
373
578
  std::vector<RE2::Arg*> args(c->number_of_capturing_groups);
@@ -392,7 +597,7 @@ static VALUE re2_scanner_scan(VALUE self) {
392
597
  VALUE result = rb_ary_new2(c->number_of_capturing_groups);
393
598
 
394
599
  for (int i = 0; i < c->number_of_capturing_groups; ++i) {
395
- if (matches[i].empty()) {
600
+ if (matches[i].data() == nullptr) {
396
601
  rb_ary_push(result, Qnil);
397
602
  } else {
398
603
  rb_ary_push(result, encoded_str_new(matches[i].data(),
@@ -404,9 +609,27 @@ static VALUE re2_scanner_scan(VALUE self) {
404
609
  /* Check whether we've exhausted the input yet. */
405
610
  c->eof = new_input_size == 0;
406
611
 
407
- /* If the match didn't advance the input, we need to do this ourselves. */
612
+ /* If the match didn't advance the input, we need to do this ourselves,
613
+ * advancing by a whole character to avoid splitting multi-byte characters.
614
+ *
615
+ * The lookup table approach is taken from RE2's own Python extension: the
616
+ * high 4 bits of a UTF-8 lead byte determine the character's byte length.
617
+ *
618
+ * See https://github.com/google/re2/blob/972a15cedd008d846f1a39b2e88ce48d7f166cbd/python/_re2.cc#L46-L48
619
+ */
408
620
  if (!input_advanced && new_input_size > 0) {
409
- c->input->remove_prefix(1);
621
+ size_t char_size = 1;
622
+
623
+ if (p->pattern->options().encoding() == RE2::Options::EncodingUTF8) {
624
+ char_size = "\1\1\1\1\1\1\1\1\1\1\1\1\2\2\3\4"
625
+ [((*c->input)[0] & 0xFF) >> 4];
626
+
627
+ if (char_size > new_input_size) {
628
+ char_size = new_input_size;
629
+ }
630
+ }
631
+
632
+ c->input->remove_prefix(char_size);
410
633
  }
411
634
 
412
635
  return result;
@@ -416,47 +639,44 @@ static VALUE re2_scanner_scan(VALUE self) {
416
639
  }
417
640
 
418
641
  static re2::StringPiece *re2_matchdata_find_match(VALUE idx, const VALUE self) {
419
- re2_matchdata *m;
420
- re2_pattern *p;
421
-
422
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
423
- TypedData_Get_Struct(m->regexp, re2_pattern, &re2_regexp_data_type, p);
642
+ re2_matchdata *m = unwrap_re2_matchdata(self);
643
+ re2_pattern *p = unwrap_re2_regexp(m->regexp);
424
644
 
425
645
  int id;
426
646
 
427
- if (FIXNUM_P(idx)) {
428
- id = FIX2INT(idx);
647
+ if (RB_INTEGER_TYPE_P(idx)) {
648
+ id = NUM2INT(idx);
429
649
  } else if (SYMBOL_P(idx)) {
430
- const std::map<std::string, int>& groups = p->pattern->NamedCapturingGroups();
431
- std::map<std::string, int>::const_iterator search = groups.find(rb_id2name(SYM2ID(idx)));
650
+ const auto& groups = p->pattern->NamedCapturingGroups();
651
+ auto search = groups.find(rb_id2name(SYM2ID(idx)));
432
652
 
433
653
  if (search != groups.end()) {
434
654
  id = search->second;
435
655
  } else {
436
- return NULL;
656
+ return nullptr;
437
657
  }
438
658
  } else {
439
659
  StringValue(idx);
440
660
 
441
- const std::map<std::string, int>& groups = p->pattern->NamedCapturingGroups();
442
- std::map<std::string, int>::const_iterator search = groups.find(std::string(RSTRING_PTR(idx), RSTRING_LEN(idx)));
661
+ const auto& groups = p->pattern->NamedCapturingGroups();
662
+ auto search = groups.find(std::string(RSTRING_PTR(idx), RSTRING_LEN(idx)));
443
663
 
444
664
  if (search != groups.end()) {
445
665
  id = search->second;
446
666
  } else {
447
- return NULL;
667
+ return nullptr;
448
668
  }
449
669
  }
450
670
 
451
671
  if (id >= 0 && id < m->number_of_matches) {
452
672
  re2::StringPiece *match = &m->matches[id];
453
673
 
454
- if (!match->empty()) {
674
+ if (match->data() != nullptr) {
455
675
  return match;
456
676
  }
457
677
  }
458
678
 
459
- return NULL;
679
+ return nullptr;
460
680
  }
461
681
 
462
682
  /*
@@ -465,14 +685,12 @@ static re2::StringPiece *re2_matchdata_find_match(VALUE idx, const VALUE self) {
465
685
  *
466
686
  * @return [Integer] the number of elements
467
687
  * @example
468
- * m = RE2::Regexp.new('(\d+)').match("bob 123")
688
+ * m = RE2::Regexp.new('(\d+)').partial_match("bob 123")
469
689
  * m.size #=> 2
470
690
  * m.length #=> 2
471
691
  */
472
692
  static VALUE re2_matchdata_size(const VALUE self) {
473
- re2_matchdata *m;
474
-
475
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
693
+ re2_matchdata *m = unwrap_re2_matchdata(self);
476
694
 
477
695
  return INT2FIX(m->number_of_matches);
478
696
  }
@@ -484,17 +702,15 @@ static VALUE re2_matchdata_size(const VALUE self) {
484
702
  * @return [Integer, nil] the offset of the start of the match or `nil` if
485
703
  * there is no such submatch
486
704
  * @example
487
- * m = RE2::Regexp.new('ob (\d+)').match("bob 123")
705
+ * m = RE2::Regexp.new('ob (\d+)').partial_match("bob 123")
488
706
  * m.begin(0) #=> 1
489
707
  * m.begin(1) #=> 4
490
708
  */
491
709
  static VALUE re2_matchdata_begin(const VALUE self, VALUE n) {
492
- re2_matchdata *m;
493
-
494
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
710
+ re2_matchdata *m = unwrap_re2_matchdata(self);
495
711
 
496
712
  re2::StringPiece *match = re2_matchdata_find_match(n, self);
497
- if (match == NULL) {
713
+ if (match == nullptr) {
498
714
  return Qnil;
499
715
  } else {
500
716
  long offset = match->data() - RSTRING_PTR(m->text);
@@ -511,17 +727,15 @@ static VALUE re2_matchdata_begin(const VALUE self, VALUE n) {
511
727
  * @return [Integer, nil] the offset of the character following the end of the
512
728
  * match or `nil` if there is no such match
513
729
  * @example
514
- * m = RE2::Regexp.new('ob (\d+) b').match("bob 123 bob")
730
+ * m = RE2::Regexp.new('ob (\d+) b').partial_match("bob 123 bob")
515
731
  * m.end(0) #=> 9
516
732
  * m.end(1) #=> 7
517
733
  */
518
734
  static VALUE re2_matchdata_end(const VALUE self, VALUE n) {
519
- re2_matchdata *m;
520
-
521
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
735
+ re2_matchdata *m = unwrap_re2_matchdata(self);
522
736
 
523
737
  re2::StringPiece *match = re2_matchdata_find_match(n, self);
524
- if (match == NULL) {
738
+ if (match == nullptr) {
525
739
  return Qnil;
526
740
  } else {
527
741
  long offset = (match->data() - RSTRING_PTR(m->text)) + match->size();
@@ -530,17 +744,129 @@ static VALUE re2_matchdata_end(const VALUE self, VALUE n) {
530
744
  }
531
745
  }
532
746
 
747
+ /*
748
+ * Returns the portion of the original string before the match.
749
+ *
750
+ * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
751
+ * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
752
+ * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
753
+ *
754
+ * @return [String] the portion of the original string before the match
755
+ * @example
756
+ * m = RE2::Regexp.new('(\d+)').partial_match("bob 123 456")
757
+ * m.pre_match #=> "bob "
758
+ */
759
+ static VALUE re2_matchdata_pre_match(const VALUE self) {
760
+ re2_matchdata *m = unwrap_re2_matchdata(self);
761
+ re2_pattern *p = unwrap_re2_regexp(m->regexp);
762
+
763
+ re2::StringPiece *match = &m->matches[0];
764
+ if (match->data() == nullptr) {
765
+ return Qnil;
766
+ }
767
+
768
+ long offset = match->data() - RSTRING_PTR(m->text);
769
+
770
+ return encoded_str_new(RSTRING_PTR(m->text), offset,
771
+ p->pattern->options().encoding());
772
+ }
773
+
774
+ /*
775
+ * Returns the portion of the original string after the match.
776
+ *
777
+ * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
778
+ * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
779
+ * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
780
+ *
781
+ * @return [String] the portion of the original string after the match
782
+ * @example
783
+ * m = RE2::Regexp.new('(\d+)').partial_match("bob 123 456")
784
+ * m.post_match #=> " 456"
785
+ */
786
+ static VALUE re2_matchdata_post_match(const VALUE self) {
787
+ re2_matchdata *m = unwrap_re2_matchdata(self);
788
+ re2_pattern *p = unwrap_re2_regexp(m->regexp);
789
+
790
+ re2::StringPiece *match = &m->matches[0];
791
+ if (match->data() == nullptr) {
792
+ return Qnil;
793
+ }
794
+
795
+ long start = (match->data() - RSTRING_PTR(m->text)) + match->size();
796
+ long remaining = RSTRING_LEN(m->text) - start;
797
+
798
+ return encoded_str_new(RSTRING_PTR(m->text) + start, remaining,
799
+ p->pattern->options().encoding());
800
+ }
801
+
802
+ /*
803
+ * Returns a two-element array containing the beginning and ending offsets of
804
+ * the nth match.
805
+ *
806
+ * @param [Integer, String, Symbol] n the name or number of the match
807
+ * @return [Array<Integer>, nil] a two-element array with the beginning and
808
+ * ending offsets of the match or `nil` if there is no such match
809
+ * @example
810
+ * m = RE2::Regexp.new('ob (\d+)').partial_match("bob 123")
811
+ * m.offset(0) #=> [1, 7]
812
+ * m.offset(1) #=> [4, 7]
813
+ */
814
+ static VALUE re2_matchdata_offset(const VALUE self, VALUE n) {
815
+ re2_matchdata *m = unwrap_re2_matchdata(self);
816
+
817
+ re2::StringPiece *match = re2_matchdata_find_match(n, self);
818
+ if (match == nullptr) {
819
+ return Qnil;
820
+ }
821
+
822
+ long start = match->data() - RSTRING_PTR(m->text);
823
+ long end_pos = start + match->size();
824
+
825
+ VALUE array = rb_ary_new2(2);
826
+ rb_ary_push(array, LONG2NUM(rb_str_sublen(m->text, start)));
827
+ rb_ary_push(array, LONG2NUM(rb_str_sublen(m->text, end_pos)));
828
+
829
+ return array;
830
+ }
831
+
832
+ /*
833
+ * Returns the length of the nth match in characters. This is equivalent to
834
+ * `m[n].length` but without allocating a new string.
835
+ *
836
+ * @param [Integer, String, Symbol] n the name or number of the match
837
+ * @return [Integer, nil] the length of the match or `nil` if there is no such
838
+ * match
839
+ * @example
840
+ * m = RE2::Regexp.new('(?P<word>\w+) (?P<number>\d+)').partial_match("alice 123")
841
+ * m.match_length(0) #=> 9
842
+ * m.match_length(1) #=> 5
843
+ * m.match_length(:number) #=> 3
844
+ */
845
+ static VALUE re2_matchdata_match_length(const VALUE self, VALUE n) {
846
+ re2_matchdata *m = unwrap_re2_matchdata(self);
847
+
848
+ re2::StringPiece *match = re2_matchdata_find_match(n, self);
849
+ if (match == nullptr) {
850
+ return Qnil;
851
+ }
852
+
853
+ long start = match->data() - RSTRING_PTR(m->text);
854
+ long end_pos = start + match->size();
855
+ long char_len = rb_str_sublen(m->text, end_pos) - rb_str_sublen(m->text, start);
856
+
857
+ return LONG2NUM(char_len);
858
+ }
859
+
533
860
  /*
534
861
  * Returns the {RE2::Regexp} used in the match.
535
862
  *
536
863
  * @return [RE2::Regexp] the regular expression used in the match
537
864
  * @example
538
- * m = RE2::Regexp.new('(\d+)').match("bob 123")
865
+ * m = RE2::Regexp.new('(\d+)').partial_match("bob 123")
539
866
  * m.regexp #=> #<RE2::Regexp /(\d+)/>
540
867
  */
541
868
  static VALUE re2_matchdata_regexp(const VALUE self) {
542
- re2_matchdata *m;
543
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
869
+ re2_matchdata *m = unwrap_re2_matchdata(self);
544
870
 
545
871
  return m->regexp;
546
872
  }
@@ -554,8 +880,7 @@ static VALUE re2_matchdata_regexp(const VALUE self) {
554
880
  * c.regexp #=> #<RE2::Regexp /(\d+)/>
555
881
  */
556
882
  static VALUE re2_scanner_regexp(const VALUE self) {
557
- re2_scanner *c;
558
- TypedData_Get_Struct(self, re2_scanner, &re2_scanner_data_type, c);
883
+ re2_scanner *c = unwrap_re2_scanner(self);
559
884
 
560
885
  return c->regexp;
561
886
  }
@@ -576,21 +901,18 @@ static VALUE re2_regexp_allocate(VALUE klass) {
576
901
  *
577
902
  * @return [Array<String, nil>] the array of matches
578
903
  * @example
579
- * m = RE2::Regexp.new('(\d+)').match("bob 123")
904
+ * m = RE2::Regexp.new('(\d+)').partial_match("bob 123")
580
905
  * m.to_a #=> ["123", "123"]
581
906
  */
582
907
  static VALUE re2_matchdata_to_a(const VALUE self) {
583
- re2_matchdata *m;
584
- re2_pattern *p;
585
-
586
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
587
- TypedData_Get_Struct(m->regexp, re2_pattern, &re2_regexp_data_type, p);
908
+ re2_matchdata *m = unwrap_re2_matchdata(self);
909
+ re2_pattern *p = unwrap_re2_regexp(m->regexp);
588
910
 
589
911
  VALUE array = rb_ary_new2(m->number_of_matches);
590
912
  for (int i = 0; i < m->number_of_matches; ++i) {
591
913
  re2::StringPiece *match = &m->matches[i];
592
914
 
593
- if (match->empty()) {
915
+ if (match->data() == nullptr) {
594
916
  rb_ary_push(array, Qnil);
595
917
  } else {
596
918
  rb_ary_push(array, encoded_str_new(match->data(), match->size(),
@@ -602,18 +924,15 @@ static VALUE re2_matchdata_to_a(const VALUE self) {
602
924
  }
603
925
 
604
926
  static VALUE re2_matchdata_nth_match(int nth, const VALUE self) {
605
- re2_matchdata *m;
606
- re2_pattern *p;
607
-
608
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
609
- TypedData_Get_Struct(m->regexp, re2_pattern, &re2_regexp_data_type, p);
927
+ re2_matchdata *m = unwrap_re2_matchdata(self);
928
+ re2_pattern *p = unwrap_re2_regexp(m->regexp);
610
929
 
611
930
  if (nth < 0 || nth >= m->number_of_matches) {
612
931
  return Qnil;
613
932
  } else {
614
933
  re2::StringPiece *match = &m->matches[nth];
615
934
 
616
- if (match->empty()) {
935
+ if (match->data() == nullptr) {
617
936
  return Qnil;
618
937
  } else {
619
938
  return encoded_str_new(match->data(), match->size(),
@@ -623,14 +942,11 @@ static VALUE re2_matchdata_nth_match(int nth, const VALUE self) {
623
942
  }
624
943
 
625
944
  static VALUE re2_matchdata_named_match(const std::string &name, const VALUE self) {
626
- re2_matchdata *m;
627
- re2_pattern *p;
945
+ re2_matchdata *m = unwrap_re2_matchdata(self);
946
+ re2_pattern *p = unwrap_re2_regexp(m->regexp);
628
947
 
629
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
630
- TypedData_Get_Struct(m->regexp, re2_pattern, &re2_regexp_data_type, p);
631
-
632
- const std::map<std::string, int>& groups = p->pattern->NamedCapturingGroups();
633
- std::map<std::string, int>::const_iterator search = groups.find(name);
948
+ const auto& groups = p->pattern->NamedCapturingGroups();
949
+ auto search = groups.find(name);
634
950
 
635
951
  if (search != groups.end()) {
636
952
  return re2_matchdata_nth_match(search->second, self);
@@ -652,7 +968,7 @@ static VALUE re2_matchdata_named_match(const std::string &name, const VALUE self
652
968
  * @param [Integer] index the index of the match to fetch
653
969
  * @return [String, nil] the specified match or `nil` if it isn't present
654
970
  * @example
655
- * m = RE2::Regexp.new('(\d+)').match("bob 123")
971
+ * m = RE2::Regexp.new('(\d+)').partial_match("bob 123")
656
972
  * m[0] #=> "123"
657
973
  *
658
974
  * @overload [](start, length)
@@ -662,7 +978,7 @@ static VALUE re2_matchdata_named_match(const std::string &name, const VALUE self
662
978
  * @param [Integer] length the number of elements to fetch
663
979
  * @return [Array<String, nil>] the specified matches
664
980
  * @example
665
- * m = RE2::Regexp.new('(\d+)').match("bob 123")
981
+ * m = RE2::Regexp.new('(\d+)').partial_match("bob 123")
666
982
  * m[0, 1] #=> ["123"]
667
983
  *
668
984
  * @overload [](range)
@@ -671,8 +987,8 @@ static VALUE re2_matchdata_named_match(const std::string &name, const VALUE self
671
987
  * @param [Range] range the range of match indexes to fetch
672
988
  * @return [Array<String, nil>] the specified matches
673
989
  * @example
674
- * m = RE2::Regexp.new('(\d+)').match("bob 123")
675
- * m[0..1] #=> "[123", "123"]
990
+ * m = RE2::Regexp.new('(\d+)').partial_match("bob 123")
991
+ * m[0..1] #=> ["123", "123"]
676
992
  *
677
993
  * @overload [](name)
678
994
  * Access a particular match by name.
@@ -680,7 +996,7 @@ static VALUE re2_matchdata_named_match(const std::string &name, const VALUE self
680
996
  * @param [String, Symbol] name the name of the match to fetch
681
997
  * @return [String, nil] the specific match or `nil` if it isn't present
682
998
  * @example
683
- * m = RE2::Regexp.new('(?P<number>\d+)').match("bob 123")
999
+ * m = RE2::Regexp.new('(?P<number>\d+)').partial_match("bob 123")
684
1000
  * m["number"] #=> "123"
685
1001
  * m[:number] #=> "123"
686
1002
  */
@@ -693,10 +1009,10 @@ static VALUE re2_matchdata_aref(int argc, VALUE *argv, const VALUE self) {
693
1009
  std::string(RSTRING_PTR(idx), RSTRING_LEN(idx)), self);
694
1010
  } else if (SYMBOL_P(idx)) {
695
1011
  return re2_matchdata_named_match(rb_id2name(SYM2ID(idx)), self);
696
- } else if (!NIL_P(rest) || !FIXNUM_P(idx) || FIX2INT(idx) < 0) {
1012
+ } else if (!NIL_P(rest) || !RB_INTEGER_TYPE_P(idx) || NUM2INT(idx) < 0) {
697
1013
  return rb_ary_aref(argc, argv, re2_matchdata_to_a(self));
698
1014
  } else {
699
- return re2_matchdata_nth_match(FIX2INT(idx), self);
1015
+ return re2_matchdata_nth_match(NUM2INT(idx), self);
700
1016
  }
701
1017
  }
702
1018
 
@@ -704,6 +1020,9 @@ static VALUE re2_matchdata_aref(int argc, VALUE *argv, const VALUE self) {
704
1020
  * Returns the entire matched string.
705
1021
  *
706
1022
  * @return [String] the entire matched string
1023
+ * @example
1024
+ * m = RE2::Regexp.new('(?P<number>\d+)').partial_match("bob 123")
1025
+ * m.to_s #=> "123"
707
1026
  */
708
1027
  static VALUE re2_matchdata_to_s(const VALUE self) {
709
1028
  return re2_matchdata_nth_match(0, self);
@@ -718,15 +1037,12 @@ static VALUE re2_matchdata_to_s(const VALUE self) {
718
1037
  *
719
1038
  * @return [String] a printable version of the match
720
1039
  * @example
721
- * m = RE2::Regexp.new('(\d+)').match("bob 123")
1040
+ * m = RE2::Regexp.new('(\d+)').partial_match("bob 123")
722
1041
  * m.inspect #=> "#<RE2::MatchData \"123\" 1:\"123\">"
723
1042
  */
724
1043
  static VALUE re2_matchdata_inspect(const VALUE self) {
725
- re2_matchdata *m;
726
- re2_pattern *p;
727
-
728
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
729
- TypedData_Get_Struct(m->regexp, re2_pattern, &re2_regexp_data_type, p);
1044
+ re2_matchdata *m = unwrap_re2_matchdata(self);
1045
+ re2_pattern *p = unwrap_re2_regexp(m->regexp);
730
1046
 
731
1047
  std::ostringstream output;
732
1048
  output << "#<RE2::MatchData";
@@ -756,7 +1072,7 @@ static VALUE re2_matchdata_inspect(const VALUE self) {
756
1072
  }
757
1073
 
758
1074
  /*
759
- * Returns the array of submatches for pattern matching.
1075
+ * Returns the array of submatches.
760
1076
  *
761
1077
  * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
762
1078
  * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
@@ -765,11 +1081,12 @@ static VALUE re2_matchdata_inspect(const VALUE self) {
765
1081
  *
766
1082
  * @return [Array<String, nil>] the array of submatches
767
1083
  * @example
768
- * m = RE2::Regexp.new('(\d+)').match("bob 123")
1084
+ * m = RE2::Regexp.new('(\d+)').partial_match("bob 123")
1085
+ * m.captures #=> ["123"]
769
1086
  * m.deconstruct #=> ["123"]
770
1087
  *
771
1088
  * @example pattern matching
772
- * case RE2::Regexp.new('(\d+) (\d+)').match("bob 123 456")
1089
+ * case RE2::Regexp.new('(\d+) (\d+)').partial_match("bob 123 456")
773
1090
  * in x, y
774
1091
  * puts "Matched #{x} #{y}"
775
1092
  * else
@@ -777,17 +1094,14 @@ static VALUE re2_matchdata_inspect(const VALUE self) {
777
1094
  * end
778
1095
  */
779
1096
  static VALUE re2_matchdata_deconstruct(const VALUE self) {
780
- re2_matchdata *m;
781
- re2_pattern *p;
782
-
783
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
784
- TypedData_Get_Struct(m->regexp, re2_pattern, &re2_regexp_data_type, p);
1097
+ re2_matchdata *m = unwrap_re2_matchdata(self);
1098
+ re2_pattern *p = unwrap_re2_regexp(m->regexp);
785
1099
 
786
1100
  VALUE array = rb_ary_new2(m->number_of_matches - 1);
787
1101
  for (int i = 1; i < m->number_of_matches; ++i) {
788
1102
  re2::StringPiece *match = &m->matches[i];
789
1103
 
790
- if (match->empty()) {
1104
+ if (match->data() == nullptr) {
791
1105
  rb_ary_push(array, Qnil);
792
1106
  } else {
793
1107
  rb_ary_push(array, encoded_str_new(match->data(), match->size(),
@@ -813,14 +1127,14 @@ static VALUE re2_matchdata_deconstruct(const VALUE self) {
813
1127
  * @param [Array<Symbol>, nil] keys an array of `Symbol` capturing group names
814
1128
  * or `nil` to return all names
815
1129
  * @example
816
- * m = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').match('123 abc')
1130
+ * m = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').partial_match('123 abc')
817
1131
  * m.deconstruct_keys(nil) #=> {numbers: "123", letters: "abc"}
818
1132
  * m.deconstruct_keys([:numbers]) #=> {numbers: "123"}
819
1133
  * m.deconstruct_keys([:fruit]) #=> {}
820
1134
  * m.deconstruct_keys([:letters, :fruit]) #=> {letters: "abc"}
821
1135
  *
822
1136
  * @example pattern matching
823
- * case RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').match('123 abc')
1137
+ * case RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').partial_match('123 abc')
824
1138
  * in numbers:, letters:
825
1139
  * puts "Numbers: #{numbers}, letters: #{letters}"
826
1140
  * else
@@ -828,20 +1142,17 @@ static VALUE re2_matchdata_deconstruct(const VALUE self) {
828
1142
  * end
829
1143
  */
830
1144
  static VALUE re2_matchdata_deconstruct_keys(const VALUE self, const VALUE keys) {
831
- re2_matchdata *m;
832
- re2_pattern *p;
833
-
834
- TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, m);
835
- TypedData_Get_Struct(m->regexp, re2_pattern, &re2_regexp_data_type, p);
1145
+ re2_matchdata *m = unwrap_re2_matchdata(self);
1146
+ re2_pattern *p = unwrap_re2_regexp(m->regexp);
836
1147
 
837
- const std::map<std::string, int>& groups = p->pattern->NamedCapturingGroups();
1148
+ const auto& groups = p->pattern->NamedCapturingGroups();
838
1149
  VALUE capturing_groups = rb_hash_new();
839
1150
 
840
1151
  if (NIL_P(keys)) {
841
- for (std::map<std::string, int>::const_iterator it = groups.begin(); it != groups.end(); ++it) {
1152
+ for (const auto& group : groups) {
842
1153
  rb_hash_aset(capturing_groups,
843
- ID2SYM(rb_intern(it->first.data())),
844
- re2_matchdata_nth_match(it->second, self));
1154
+ ID2SYM(rb_intern2(group.first.data(), group.first.size())),
1155
+ re2_matchdata_nth_match(group.second, self));
845
1156
  }
846
1157
  } else {
847
1158
  Check_Type(keys, T_ARRAY);
@@ -851,7 +1162,7 @@ static VALUE re2_matchdata_deconstruct_keys(const VALUE self, const VALUE keys)
851
1162
  VALUE key = rb_ary_entry(keys, i);
852
1163
  Check_Type(key, T_SYMBOL);
853
1164
  const char *name = rb_id2name(SYM2ID(key));
854
- std::map<std::string, int>::const_iterator search = groups.find(name);
1165
+ auto search = groups.find(name);
855
1166
 
856
1167
  if (search != groups.end()) {
857
1168
  rb_hash_aset(capturing_groups, key, re2_matchdata_nth_match(search->second, self));
@@ -865,6 +1176,151 @@ static VALUE re2_matchdata_deconstruct_keys(const VALUE self, const VALUE keys)
865
1176
  return capturing_groups;
866
1177
  }
867
1178
 
1179
+ /*
1180
+ * Returns a hash of capturing group names to matched strings.
1181
+ *
1182
+ * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
1183
+ * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
1184
+ * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
1185
+ *
1186
+ * @overload named_captures
1187
+ * Returns a hash with string keys.
1188
+ *
1189
+ * @return [Hash] a hash of capturing group names to matching strings
1190
+ * @example
1191
+ * m = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').partial_match('123 abc')
1192
+ * m.named_captures #=> {"numbers" => "123", "letters" => "abc"}
1193
+ *
1194
+ * @overload named_captures(symbolize_names:)
1195
+ * Returns a hash with string or symbol keys.
1196
+ *
1197
+ * @param [Boolean] symbolize_names whether to return group names as symbols
1198
+ * @return [Hash] a hash of capturing group names to matching strings
1199
+ * @example
1200
+ * m = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').partial_match('123 abc')
1201
+ * m.named_captures
1202
+ * #=> {"numbers" => "123", "letters" => "abc"}
1203
+ * m.named_captures(symbolize_names: true) #=> {numbers: "123", letters: "abc"}
1204
+ */
1205
+ static VALUE re2_matchdata_named_captures(int argc, VALUE *argv, const VALUE self) {
1206
+ VALUE opts;
1207
+ rb_scan_args(argc, argv, "0:", &opts);
1208
+
1209
+ bool symbolize = false;
1210
+ if (!NIL_P(opts)) {
1211
+ VALUE sym = rb_hash_aref(opts, ID2SYM(id_symbolize_names));
1212
+ symbolize = RTEST(sym);
1213
+ }
1214
+
1215
+ re2_matchdata *m = unwrap_re2_matchdata(self);
1216
+ re2_pattern *p = unwrap_re2_regexp(m->regexp);
1217
+
1218
+ const auto& groups = p->pattern->NamedCapturingGroups();
1219
+ VALUE result = rb_hash_new();
1220
+
1221
+ for (const auto& group : groups) {
1222
+ VALUE key;
1223
+ if (symbolize) {
1224
+ key = ID2SYM(rb_intern2(group.first.data(), group.first.size()));
1225
+ } else {
1226
+ key = encoded_str_new(group.first.data(), group.first.size(),
1227
+ p->pattern->options().encoding());
1228
+ }
1229
+ rb_hash_aset(result, key, re2_matchdata_nth_match(group.second, self));
1230
+ }
1231
+
1232
+ return result;
1233
+ }
1234
+
1235
+ /*
1236
+ * Returns an array of names of named capturing groups. Names are returned in
1237
+ * alphabetical order rather than definition order, as RE2 stores named groups
1238
+ * internally in a sorted map.
1239
+ *
1240
+ * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
1241
+ * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
1242
+ * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
1243
+ *
1244
+ * @return [Array<String>] an array of names of named capturing groups
1245
+ * @example
1246
+ * m = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').partial_match('123 abc')
1247
+ * m.names #=> ["letters", "numbers"]
1248
+ */
1249
+ static VALUE re2_matchdata_names(const VALUE self) {
1250
+ re2_matchdata *m = unwrap_re2_matchdata(self);
1251
+
1252
+ return re2_regexp_names(m->regexp);
1253
+ }
1254
+
1255
+ /*
1256
+ * Returns an array of match values at the given indices or names.
1257
+ *
1258
+ * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
1259
+ * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
1260
+ * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
1261
+ *
1262
+ * @param [Integer, String, Symbol] indexes the indices or names of
1263
+ * the matches to fetch
1264
+ * @return [Array<String, nil>] the values at the given indices or names
1265
+ * @example
1266
+ * m = RE2::Regexp.new('(?P<a>\d+) (?P<b>\d+)').partial_match("123 456")
1267
+ * m.values_at(1, 2) #=> ["123", "456"]
1268
+ * m.values_at(:a, :b) #=> ["123", "456"]
1269
+ * m.values_at(1, :b) #=> ["123", "456"]
1270
+ */
1271
+ static VALUE re2_matchdata_values_at(int argc, VALUE *argv, const VALUE self) {
1272
+ unwrap_re2_matchdata(self);
1273
+
1274
+ VALUE result = rb_ary_new2(argc);
1275
+
1276
+ for (int i = 0; i < argc; ++i) {
1277
+ VALUE idx = argv[i];
1278
+
1279
+ if (TYPE(idx) == T_STRING) {
1280
+ rb_ary_push(result, re2_matchdata_named_match(
1281
+ std::string(RSTRING_PTR(idx), RSTRING_LEN(idx)), self));
1282
+ } else if (SYMBOL_P(idx)) {
1283
+ rb_ary_push(result, re2_matchdata_named_match(
1284
+ rb_id2name(SYM2ID(idx)), self));
1285
+ } else {
1286
+ rb_ary_push(result, re2_matchdata_nth_match(NUM2INT(idx), self));
1287
+ }
1288
+ }
1289
+
1290
+ return result;
1291
+ }
1292
+
1293
+ static VALUE re2_matchdata_initialize_copy(VALUE self, VALUE other) {
1294
+ re2_matchdata *self_m;
1295
+ re2_matchdata *other_m = unwrap_re2_matchdata(other);
1296
+
1297
+ TypedData_Get_Struct(self, re2_matchdata, &re2_matchdata_data_type, self_m);
1298
+
1299
+ if (self_m->matches) {
1300
+ delete[] self_m->matches;
1301
+ self_m->matches = nullptr;
1302
+ }
1303
+
1304
+ self_m->number_of_matches = other_m->number_of_matches;
1305
+ RB_OBJ_WRITE(self, &self_m->regexp, other_m->regexp);
1306
+ RB_OBJ_WRITE(self, &self_m->text, other_m->text);
1307
+
1308
+ if (other_m->matches) {
1309
+ self_m->matches = new(std::nothrow) re2::StringPiece[other_m->number_of_matches];
1310
+ if (self_m->matches == nullptr) {
1311
+ rb_raise(rb_eNoMemError,
1312
+ "not enough memory to allocate StringPiece for matches");
1313
+ }
1314
+ for (int i = 0; i < other_m->number_of_matches; ++i) {
1315
+ self_m->matches[i] = other_m->matches[i];
1316
+ }
1317
+ } else {
1318
+ self_m->matches = nullptr;
1319
+ }
1320
+
1321
+ return self;
1322
+ }
1323
+
868
1324
  /*
869
1325
  * Shorthand to compile a new {RE2::Regexp}.
870
1326
  *
@@ -920,6 +1376,13 @@ static VALUE re2_regexp_initialize(int argc, VALUE *argv, VALUE self) {
920
1376
 
921
1377
  TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
922
1378
 
1379
+ rb_check_frozen(self);
1380
+
1381
+ if (p->pattern) {
1382
+ delete p->pattern;
1383
+ p->pattern = nullptr;
1384
+ }
1385
+
923
1386
  if (RTEST(options)) {
924
1387
  RE2::Options re2_options;
925
1388
  parse_re2_options(&re2_options, options);
@@ -931,10 +1394,36 @@ static VALUE re2_regexp_initialize(int argc, VALUE *argv, VALUE self) {
931
1394
  re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)));
932
1395
  }
933
1396
 
934
- if (p->pattern == 0) {
1397
+ if (p->pattern == nullptr) {
1398
+ rb_raise(rb_eNoMemError, "not enough memory to allocate RE2 object");
1399
+ }
1400
+
1401
+ rb_obj_freeze(self);
1402
+
1403
+ return self;
1404
+ }
1405
+
1406
+ static VALUE re2_regexp_initialize_copy(VALUE self, VALUE other) {
1407
+ re2_pattern *self_p;
1408
+ re2_pattern *other_p = unwrap_re2_regexp(other);
1409
+
1410
+ TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, self_p);
1411
+
1412
+ rb_check_frozen(self);
1413
+
1414
+ if (self_p->pattern) {
1415
+ delete self_p->pattern;
1416
+ self_p->pattern = nullptr;
1417
+ }
1418
+
1419
+ self_p->pattern = new(std::nothrow) RE2(other_p->pattern->pattern(),
1420
+ other_p->pattern->options());
1421
+ if (self_p->pattern == nullptr) {
935
1422
  rb_raise(rb_eNoMemError, "not enough memory to allocate RE2 object");
936
1423
  }
937
1424
 
1425
+ rb_obj_freeze(self);
1426
+
938
1427
  return self;
939
1428
  }
940
1429
 
@@ -952,9 +1441,7 @@ static VALUE re2_regexp_initialize(int argc, VALUE *argv, VALUE self) {
952
1441
  * re2.inspect #=> "#<RE2::Regexp /woo?/>"
953
1442
  */
954
1443
  static VALUE re2_regexp_inspect(const VALUE self) {
955
- re2_pattern *p;
956
-
957
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1444
+ re2_pattern *p = unwrap_re2_regexp(self);
958
1445
 
959
1446
  std::ostringstream output;
960
1447
 
@@ -977,8 +1464,7 @@ static VALUE re2_regexp_inspect(const VALUE self) {
977
1464
  * re2.to_s #=> "woo?"
978
1465
  */
979
1466
  static VALUE re2_regexp_to_s(const VALUE self) {
980
- re2_pattern *p;
981
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1467
+ re2_pattern *p = unwrap_re2_regexp(self);
982
1468
 
983
1469
  return encoded_str_new(p->pattern->pattern().data(),
984
1470
  p->pattern->pattern().size(),
@@ -994,8 +1480,7 @@ static VALUE re2_regexp_to_s(const VALUE self) {
994
1480
  * re2.ok? #=> true
995
1481
  */
996
1482
  static VALUE re2_regexp_ok(const VALUE self) {
997
- re2_pattern *p;
998
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1483
+ re2_pattern *p = unwrap_re2_regexp(self);
999
1484
 
1000
1485
  return BOOL2RUBY(p->pattern->ok());
1001
1486
  }
@@ -1010,8 +1495,7 @@ static VALUE re2_regexp_ok(const VALUE self) {
1010
1495
  * re2.utf8? #=> true
1011
1496
  */
1012
1497
  static VALUE re2_regexp_utf8(const VALUE self) {
1013
- re2_pattern *p;
1014
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1498
+ re2_pattern *p = unwrap_re2_regexp(self);
1015
1499
 
1016
1500
  return BOOL2RUBY(p->pattern->options().encoding() == RE2::Options::EncodingUTF8);
1017
1501
  }
@@ -1026,8 +1510,7 @@ static VALUE re2_regexp_utf8(const VALUE self) {
1026
1510
  * re2.posix_syntax? #=> true
1027
1511
  */
1028
1512
  static VALUE re2_regexp_posix_syntax(const VALUE self) {
1029
- re2_pattern *p;
1030
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1513
+ re2_pattern *p = unwrap_re2_regexp(self);
1031
1514
 
1032
1515
  return BOOL2RUBY(p->pattern->options().posix_syntax());
1033
1516
  }
@@ -1042,8 +1525,7 @@ static VALUE re2_regexp_posix_syntax(const VALUE self) {
1042
1525
  * re2.longest_match? #=> true
1043
1526
  */
1044
1527
  static VALUE re2_regexp_longest_match(const VALUE self) {
1045
- re2_pattern *p;
1046
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1528
+ re2_pattern *p = unwrap_re2_regexp(self);
1047
1529
 
1048
1530
  return BOOL2RUBY(p->pattern->options().longest_match());
1049
1531
  }
@@ -1058,8 +1540,7 @@ static VALUE re2_regexp_longest_match(const VALUE self) {
1058
1540
  * re2.log_errors? #=> true
1059
1541
  */
1060
1542
  static VALUE re2_regexp_log_errors(const VALUE self) {
1061
- re2_pattern *p;
1062
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1543
+ re2_pattern *p = unwrap_re2_regexp(self);
1063
1544
 
1064
1545
  return BOOL2RUBY(p->pattern->options().log_errors());
1065
1546
  }
@@ -1073,8 +1554,7 @@ static VALUE re2_regexp_log_errors(const VALUE self) {
1073
1554
  * re2.max_mem #=> 1024
1074
1555
  */
1075
1556
  static VALUE re2_regexp_max_mem(const VALUE self) {
1076
- re2_pattern *p;
1077
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1557
+ re2_pattern *p = unwrap_re2_regexp(self);
1078
1558
 
1079
1559
  return INT2FIX(p->pattern->options().max_mem());
1080
1560
  }
@@ -1089,8 +1569,7 @@ static VALUE re2_regexp_max_mem(const VALUE self) {
1089
1569
  * re2.literal? #=> true
1090
1570
  */
1091
1571
  static VALUE re2_regexp_literal(const VALUE self) {
1092
- re2_pattern *p;
1093
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1572
+ re2_pattern *p = unwrap_re2_regexp(self);
1094
1573
 
1095
1574
  return BOOL2RUBY(p->pattern->options().literal());
1096
1575
  }
@@ -1105,8 +1584,7 @@ static VALUE re2_regexp_literal(const VALUE self) {
1105
1584
  * re2.never_nl? #=> true
1106
1585
  */
1107
1586
  static VALUE re2_regexp_never_nl(const VALUE self) {
1108
- re2_pattern *p;
1109
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1587
+ re2_pattern *p = unwrap_re2_regexp(self);
1110
1588
 
1111
1589
  return BOOL2RUBY(p->pattern->options().never_nl());
1112
1590
  }
@@ -1121,8 +1599,7 @@ static VALUE re2_regexp_never_nl(const VALUE self) {
1121
1599
  * re2.case_sensitive? #=> true
1122
1600
  */
1123
1601
  static VALUE re2_regexp_case_sensitive(const VALUE self) {
1124
- re2_pattern *p;
1125
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1602
+ re2_pattern *p = unwrap_re2_regexp(self);
1126
1603
 
1127
1604
  return BOOL2RUBY(p->pattern->options().case_sensitive());
1128
1605
  }
@@ -1151,8 +1628,7 @@ static VALUE re2_regexp_case_insensitive(const VALUE self) {
1151
1628
  * re2.perl_classes? #=> true
1152
1629
  */
1153
1630
  static VALUE re2_regexp_perl_classes(const VALUE self) {
1154
- re2_pattern *p;
1155
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1631
+ re2_pattern *p = unwrap_re2_regexp(self);
1156
1632
 
1157
1633
  return BOOL2RUBY(p->pattern->options().perl_classes());
1158
1634
  }
@@ -1167,8 +1643,7 @@ static VALUE re2_regexp_perl_classes(const VALUE self) {
1167
1643
  * re2.word_boundary? #=> true
1168
1644
  */
1169
1645
  static VALUE re2_regexp_word_boundary(const VALUE self) {
1170
- re2_pattern *p;
1171
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1646
+ re2_pattern *p = unwrap_re2_regexp(self);
1172
1647
 
1173
1648
  return BOOL2RUBY(p->pattern->options().word_boundary());
1174
1649
  }
@@ -1183,8 +1658,7 @@ static VALUE re2_regexp_word_boundary(const VALUE self) {
1183
1658
  * re2.one_line? #=> true
1184
1659
  */
1185
1660
  static VALUE re2_regexp_one_line(const VALUE self) {
1186
- re2_pattern *p;
1187
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1661
+ re2_pattern *p = unwrap_re2_regexp(self);
1188
1662
 
1189
1663
  return BOOL2RUBY(p->pattern->options().one_line());
1190
1664
  }
@@ -1196,8 +1670,7 @@ static VALUE re2_regexp_one_line(const VALUE self) {
1196
1670
  * @return [String, nil] the error string or `nil`
1197
1671
  */
1198
1672
  static VALUE re2_regexp_error(const VALUE self) {
1199
- re2_pattern *p;
1200
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1673
+ re2_pattern *p = unwrap_re2_regexp(self);
1201
1674
 
1202
1675
  if (p->pattern->ok()) {
1203
1676
  return Qnil;
@@ -1217,8 +1690,7 @@ static VALUE re2_regexp_error(const VALUE self) {
1217
1690
  * @return [String, nil] the offending portion of the regexp or `nil`
1218
1691
  */
1219
1692
  static VALUE re2_regexp_error_arg(const VALUE self) {
1220
- re2_pattern *p;
1221
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1693
+ re2_pattern *p = unwrap_re2_regexp(self);
1222
1694
 
1223
1695
  if (p->pattern->ok()) {
1224
1696
  return Qnil;
@@ -1237,8 +1709,7 @@ static VALUE re2_regexp_error_arg(const VALUE self) {
1237
1709
  * @return [Integer] the regexp "cost"
1238
1710
  */
1239
1711
  static VALUE re2_regexp_program_size(const VALUE self) {
1240
- re2_pattern *p;
1241
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1712
+ re2_pattern *p = unwrap_re2_regexp(self);
1242
1713
 
1243
1714
  return INT2FIX(p->pattern->ProgramSize());
1244
1715
  }
@@ -1249,9 +1720,7 @@ static VALUE re2_regexp_program_size(const VALUE self) {
1249
1720
  * @return [Hash] the options
1250
1721
  */
1251
1722
  static VALUE re2_regexp_options(const VALUE self) {
1252
- re2_pattern *p;
1253
-
1254
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1723
+ re2_pattern *p = unwrap_re2_regexp(self);
1255
1724
  VALUE options = rb_hash_new();
1256
1725
 
1257
1726
  rb_hash_aset(options, ID2SYM(id_utf8),
@@ -1301,8 +1770,7 @@ static VALUE re2_regexp_options(const VALUE self) {
1301
1770
  * @return [Integer] the number of capturing subpatterns
1302
1771
  */
1303
1772
  static VALUE re2_regexp_number_of_capturing_groups(const VALUE self) {
1304
- re2_pattern *p;
1305
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1773
+ re2_pattern *p = unwrap_re2_regexp(self);
1306
1774
 
1307
1775
  return INT2FIX(p->pattern->NumberOfCapturingGroups());
1308
1776
  }
@@ -1317,17 +1785,15 @@ static VALUE re2_regexp_number_of_capturing_groups(const VALUE self) {
1317
1785
  * @return [Hash] a hash of names to capturing indices
1318
1786
  */
1319
1787
  static VALUE re2_regexp_named_capturing_groups(const VALUE self) {
1320
- re2_pattern *p;
1321
-
1322
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1323
- const std::map<std::string, int>& groups = p->pattern->NamedCapturingGroups();
1788
+ re2_pattern *p = unwrap_re2_regexp(self);
1789
+ const auto& groups = p->pattern->NamedCapturingGroups();
1324
1790
  VALUE capturing_groups = rb_hash_new();
1325
1791
 
1326
- for (std::map<std::string, int>::const_iterator it = groups.begin(); it != groups.end(); ++it) {
1792
+ for (const auto& group : groups) {
1327
1793
  rb_hash_aset(capturing_groups,
1328
- encoded_str_new(it->first.data(), it->first.size(),
1794
+ encoded_str_new(group.first.data(), group.first.size(),
1329
1795
  p->pattern->options().encoding()),
1330
- INT2FIX(it->second));
1796
+ INT2FIX(group.second));
1331
1797
  }
1332
1798
 
1333
1799
  return capturing_groups;
@@ -1422,18 +1888,19 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
1422
1888
 
1423
1889
  rb_scan_args(argc, argv, "11", &text, &options);
1424
1890
 
1425
- /* Ensure text is a string. */
1891
+ /* Coerce and freeze text to prevent mutation. */
1426
1892
  StringValue(text);
1893
+ text = rb_str_new_frozen(text);
1427
1894
 
1428
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1895
+ p = unwrap_re2_regexp(self);
1429
1896
 
1430
1897
  int n;
1431
- int startpos = 0;
1432
- int endpos = RSTRING_LEN(text);
1898
+ size_t startpos = 0;
1899
+ size_t endpos = RSTRING_LEN(text);
1433
1900
  RE2::Anchor anchor = RE2::UNANCHORED;
1434
1901
 
1435
1902
  if (RTEST(options)) {
1436
- if (FIXNUM_P(options)) {
1903
+ if (RB_INTEGER_TYPE_P(options)) {
1437
1904
  n = NUM2INT(options);
1438
1905
 
1439
1906
  if (n < 0) {
@@ -1447,13 +1914,13 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
1447
1914
  VALUE endpos_option = rb_hash_aref(options, ID2SYM(id_endpos));
1448
1915
  if (!NIL_P(endpos_option)) {
1449
1916
  #ifdef HAVE_ENDPOS_ARGUMENT
1450
- Check_Type(endpos_option, T_FIXNUM);
1917
+ ssize_t endpos_value = NUM2SSIZET(endpos_option);
1451
1918
 
1452
- endpos = NUM2INT(endpos_option);
1453
-
1454
- if (endpos < 0) {
1919
+ if (endpos_value < 0) {
1455
1920
  rb_raise(rb_eArgError, "endpos should be >= 0");
1456
1921
  }
1922
+
1923
+ endpos = static_cast<size_t>(endpos_value);
1457
1924
  #else
1458
1925
  rb_raise(re2_eRegexpUnsupportedError, "current version of RE2::Match() does not support endpos argument");
1459
1926
  #endif
@@ -1477,8 +1944,6 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
1477
1944
 
1478
1945
  VALUE submatches_option = rb_hash_aref(options, ID2SYM(id_submatches));
1479
1946
  if (!NIL_P(submatches_option)) {
1480
- Check_Type(submatches_option, T_FIXNUM);
1481
-
1482
1947
  n = NUM2INT(submatches_option);
1483
1948
 
1484
1949
  if (n < 0) {
@@ -1494,13 +1959,13 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
1494
1959
 
1495
1960
  VALUE startpos_option = rb_hash_aref(options, ID2SYM(id_startpos));
1496
1961
  if (!NIL_P(startpos_option)) {
1497
- Check_Type(startpos_option, T_FIXNUM);
1498
-
1499
- startpos = NUM2INT(startpos_option);
1962
+ ssize_t startpos_value = NUM2SSIZET(startpos_option);
1500
1963
 
1501
- if (startpos < 0) {
1964
+ if (startpos_value < 0) {
1502
1965
  rb_raise(rb_eArgError, "startpos should be >= 0");
1503
1966
  }
1967
+
1968
+ startpos = static_cast<size_t>(startpos_value);
1504
1969
  }
1505
1970
  }
1506
1971
  } else {
@@ -1515,49 +1980,50 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
1515
1980
  rb_raise(rb_eArgError, "startpos should be <= endpos");
1516
1981
  }
1517
1982
 
1518
- if (n == 0) {
1519
- #ifdef HAVE_ENDPOS_ARGUMENT
1520
- bool matched = p->pattern->Match(
1521
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)),
1522
- startpos, endpos, anchor, 0, 0);
1523
- #else
1524
- bool matched = p->pattern->Match(
1525
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)),
1526
- startpos, anchor, 0, 0);
1983
+ #ifndef HAVE_ENDPOS_ARGUMENT
1984
+ /* Old RE2's Match() takes int startpos. Reject values that would overflow. */
1985
+ if (startpos > INT_MAX) {
1986
+ rb_raise(rb_eRangeError, "startpos should be <= %d", INT_MAX);
1987
+ }
1527
1988
  #endif
1989
+
1990
+ if (n == 0) {
1991
+ bool matched = re2_match_without_gvl(
1992
+ p->pattern, text, startpos, endpos, anchor, 0, 0);
1993
+ RB_GC_GUARD(text);
1994
+
1528
1995
  return BOOL2RUBY(matched);
1529
1996
  } else {
1997
+ if (n == INT_MAX) {
1998
+ rb_raise(rb_eRangeError, "number of matches should be < %d", INT_MAX);
1999
+ }
2000
+
1530
2001
  /* Because match returns the whole match as well. */
1531
2002
  n += 1;
1532
2003
 
1533
- VALUE matchdata = rb_class_new_instance(0, 0, re2_cMatchData);
1534
- TypedData_Get_Struct(matchdata, re2_matchdata, &re2_matchdata_data_type, m);
1535
- m->matches = new(std::nothrow) re2::StringPiece[n];
1536
- RB_OBJ_WRITE(matchdata, &m->regexp, self);
1537
- if (!RTEST(rb_obj_frozen_p(text))) {
1538
- text = rb_str_freeze(rb_str_dup(text));
1539
- }
1540
- RB_OBJ_WRITE(matchdata, &m->text, text);
1541
-
1542
- if (m->matches == 0) {
2004
+ re2::StringPiece *matches = new(std::nothrow) re2::StringPiece[n];
2005
+ if (matches == nullptr) {
1543
2006
  rb_raise(rb_eNoMemError,
1544
2007
  "not enough memory to allocate StringPieces for matches");
1545
2008
  }
1546
2009
 
1547
- m->number_of_matches = n;
2010
+ bool matched = re2_match_without_gvl(
2011
+ p->pattern, text, startpos, endpos, anchor, matches, n);
2012
+ RB_GC_GUARD(text);
1548
2013
 
1549
- #ifdef HAVE_ENDPOS_ARGUMENT
1550
- bool matched = p->pattern->Match(
1551
- re2::StringPiece(RSTRING_PTR(m->text), RSTRING_LEN(m->text)),
1552
- startpos, endpos, anchor, m->matches, n);
1553
- #else
1554
- bool matched = p->pattern->Match(
1555
- re2::StringPiece(RSTRING_PTR(m->text), RSTRING_LEN(m->text)),
1556
- startpos, anchor, m->matches, n);
1557
- #endif
1558
2014
  if (matched) {
2015
+ VALUE matchdata = rb_class_new_instance(0, 0, re2_cMatchData);
2016
+ TypedData_Get_Struct(matchdata, re2_matchdata, &re2_matchdata_data_type, m);
2017
+
2018
+ RB_OBJ_WRITE(matchdata, &m->regexp, self);
2019
+ RB_OBJ_WRITE(matchdata, &m->text, text);
2020
+ m->matches = matches;
2021
+ m->number_of_matches = n;
2022
+
1559
2023
  return matchdata;
1560
2024
  } else {
2025
+ delete[] matches;
2026
+
1561
2027
  return Qnil;
1562
2028
  }
1563
2029
  }
@@ -1568,19 +2034,20 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
1568
2034
  * {https://github.com/google/re2/blob/bc0faab533e2b27b85b8ad312abf061e33ed6b5d/re2/re2.h#L413-L427
1569
2035
  * `PartialMatch`}.
1570
2036
  *
2037
+ * @param [String] text the text to search
1571
2038
  * @return [Boolean] whether the match was successful
1572
2039
  * @raise [TypeError] if text cannot be coerced to a `String`
1573
2040
  */
1574
2041
  static VALUE re2_regexp_match_p(const VALUE self, VALUE text) {
1575
- re2_pattern *p;
1576
-
1577
- /* Ensure text is a string. */
1578
2042
  StringValue(text);
2043
+ text = rb_str_new_frozen(text);
1579
2044
 
1580
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
2045
+ re2_pattern *p = unwrap_re2_regexp(self);
2046
+ bool matched = re2_match_without_gvl(
2047
+ p->pattern, text, 0, RSTRING_LEN(text), RE2::UNANCHORED, 0, 0);
2048
+ RB_GC_GUARD(text);
1581
2049
 
1582
- return BOOL2RUBY(RE2::PartialMatch(
1583
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)), *p->pattern));
2050
+ return BOOL2RUBY(matched);
1584
2051
  }
1585
2052
 
1586
2053
  /*
@@ -1588,19 +2055,20 @@ static VALUE re2_regexp_match_p(const VALUE self, VALUE text) {
1588
2055
  * {https://github.com/google/re2/blob/bc0faab533e2b27b85b8ad312abf061e33ed6b5d/re2/re2.h#L376-L411
1589
2056
  * `FullMatch`}.
1590
2057
  *
2058
+ * @param [String] text the text to search
1591
2059
  * @return [Boolean] whether the match was successful
1592
2060
  * @raise [TypeError] if text cannot be coerced to a `String`
1593
2061
  */
1594
2062
  static VALUE re2_regexp_full_match_p(const VALUE self, VALUE text) {
1595
- re2_pattern *p;
1596
-
1597
- /* Ensure text is a string. */
1598
2063
  StringValue(text);
2064
+ text = rb_str_new_frozen(text);
1599
2065
 
1600
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
2066
+ re2_pattern *p = unwrap_re2_regexp(self);
2067
+ bool matched = re2_match_without_gvl(
2068
+ p->pattern, text, 0, RSTRING_LEN(text), RE2::ANCHOR_BOTH, 0, 0);
2069
+ RB_GC_GUARD(text);
1601
2070
 
1602
- return BOOL2RUBY(RE2::FullMatch(
1603
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)), *p->pattern));
2071
+ return BOOL2RUBY(matched);
1604
2072
  }
1605
2073
 
1606
2074
  /*
@@ -1616,20 +2084,22 @@ static VALUE re2_regexp_full_match_p(const VALUE self, VALUE text) {
1616
2084
  * #=> #<RE2::Scanner:0x0000000000000001>
1617
2085
  */
1618
2086
  static VALUE re2_regexp_scan(const VALUE self, VALUE text) {
1619
- /* Ensure text is a string. */
1620
2087
  StringValue(text);
2088
+ text = rb_str_new_frozen(text);
1621
2089
 
1622
- re2_pattern *p;
2090
+ re2_pattern *p = unwrap_re2_regexp(self);
1623
2091
  re2_scanner *c;
1624
-
1625
- TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1626
2092
  VALUE scanner = rb_class_new_instance(0, 0, re2_cScanner);
1627
2093
  TypedData_Get_Struct(scanner, re2_scanner, &re2_scanner_data_type, c);
1628
2094
 
1629
- c->input = new(std::nothrow) re2::StringPiece(
1630
- RSTRING_PTR(text), RSTRING_LEN(text));
1631
2095
  RB_OBJ_WRITE(scanner, &c->regexp, self);
1632
2096
  RB_OBJ_WRITE(scanner, &c->text, text);
2097
+ c->input = new(std::nothrow) re2::StringPiece(
2098
+ RSTRING_PTR(c->text), RSTRING_LEN(c->text));
2099
+ if (c->input == nullptr) {
2100
+ rb_raise(rb_eNoMemError,
2101
+ "not enough memory to allocate StringPiece for input");
2102
+ }
1633
2103
 
1634
2104
  if (p->pattern->ok()) {
1635
2105
  c->number_of_capturing_groups = p->pattern->NumberOfCapturingGroups();
@@ -1678,40 +2148,59 @@ static VALUE re2_regexp_match_has_endpos_argument_p(VALUE) {
1678
2148
  * @raise [TypeError] if the given rewrite or pattern (if not provided as a
1679
2149
  * {RE2::Regexp}) cannot be coerced to `String`s
1680
2150
  * @example
1681
- * RE2.Replace("hello there", "hello", "howdy") #=> "howdy there"
2151
+ * RE2.replace("hello there", "hello", "howdy") #=> "howdy there"
1682
2152
  * re2 = RE2::Regexp.new("hel+o")
1683
- * RE2.Replace("hello there", re2, "yo") #=> "yo there"
2153
+ * RE2.replace("hello there", re2, "yo") #=> "yo there"
1684
2154
  */
1685
- static VALUE re2_Replace(VALUE, VALUE str, VALUE pattern,
2155
+ static VALUE re2_replace(VALUE, VALUE str, VALUE pattern,
1686
2156
  VALUE rewrite) {
1687
- /* Ensure rewrite is a string. */
1688
- StringValue(rewrite);
1689
-
1690
- re2_pattern *p;
2157
+ re2_pattern *p = nullptr;
1691
2158
 
1692
- /* Take a copy of str so it can be modified in-place by
1693
- * RE2::Replace.
2159
+ /* Coerce and freeze all arguments before any C++ allocations so that any
2160
+ * Ruby exceptions (via longjmp) cannot bypass C++ destructors and leak
2161
+ * memory, and later coercions cannot mutate earlier strings.
1694
2162
  */
1695
2163
  StringValue(str);
2164
+ str = rb_str_new_frozen(str);
2165
+ if (rb_obj_is_kind_of(pattern, re2_cRegexp)) {
2166
+ p = unwrap_re2_regexp(pattern);
2167
+ } else {
2168
+ StringValue(pattern);
2169
+ pattern = rb_str_new_frozen(pattern);
2170
+ }
2171
+ StringValue(rewrite);
2172
+ rewrite = rb_str_new_frozen(rewrite);
2173
+
2174
+ /* Take a copy of str so it can be modified in-place by RE2::Replace. */
1696
2175
  std::string str_as_string(RSTRING_PTR(str), RSTRING_LEN(str));
1697
2176
 
1698
- /* Do the replacement. */
1699
- if (rb_obj_is_kind_of(pattern, re2_cRegexp)) {
1700
- TypedData_Get_Struct(pattern, re2_pattern, &re2_regexp_data_type, p);
1701
- RE2::Replace(&str_as_string, *p->pattern,
1702
- re2::StringPiece(RSTRING_PTR(rewrite), RSTRING_LEN(rewrite)));
2177
+ nogvl_replace_arg arg;
2178
+ arg.str = &str_as_string;
2179
+ if (p) {
2180
+ arg.pattern = p->pattern;
2181
+ } else {
2182
+ arg.pattern = nullptr;
2183
+ arg.string_pattern = re2::StringPiece(
2184
+ RSTRING_PTR(pattern), RSTRING_LEN(pattern));
2185
+ }
2186
+ arg.rewrite = re2::StringPiece(
2187
+ RSTRING_PTR(rewrite), RSTRING_LEN(rewrite));
2188
+
2189
+ #ifdef _WIN32
2190
+ nogvl_replace(&arg);
2191
+ #else
2192
+ rb_thread_call_without_gvl(nogvl_replace, &arg, NULL, NULL);
2193
+ #endif
1703
2194
 
2195
+ RB_GC_GUARD(rewrite);
2196
+ RB_GC_GUARD(pattern);
2197
+
2198
+ if (p) {
1704
2199
  return encoded_str_new(str_as_string.data(), str_as_string.size(),
1705
2200
  p->pattern->options().encoding());
1706
2201
  } else {
1707
- /* Ensure pattern is a string. */
1708
- StringValue(pattern);
1709
-
1710
- RE2::Replace(&str_as_string,
1711
- re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)),
1712
- re2::StringPiece(RSTRING_PTR(rewrite), RSTRING_LEN(rewrite)));
1713
-
1714
- return encoded_str_new(str_as_string.data(), str_as_string.size(), RE2::Options::EncodingUTF8);
2202
+ return encoded_str_new(str_as_string.data(), str_as_string.size(),
2203
+ RE2::Options::EncodingUTF8);
1715
2204
  }
1716
2205
  }
1717
2206
 
@@ -1732,38 +2221,136 @@ static VALUE re2_Replace(VALUE, VALUE str, VALUE pattern,
1732
2221
  * @return [String] the resulting string
1733
2222
  * @example
1734
2223
  * re2 = RE2::Regexp.new("oo?")
1735
- * RE2.GlobalReplace("whoops-doops", re2, "e") #=> "wheps-deps"
1736
- * RE2.GlobalReplace("hello there", "e", "i") #=> "hillo thiri"
2224
+ * RE2.global_replace("whoops-doops", re2, "e") #=> "wheps-deps"
2225
+ * RE2.global_replace("hello there", "e", "i") #=> "hillo thiri"
1737
2226
  */
1738
- static VALUE re2_GlobalReplace(VALUE, VALUE str, VALUE pattern,
2227
+ static VALUE re2_global_replace(VALUE, VALUE str, VALUE pattern,
1739
2228
  VALUE rewrite) {
1740
- /* Ensure rewrite is a string. */
2229
+ re2_pattern *p = nullptr;
2230
+
2231
+ /* Coerce and freeze all arguments before any C++ allocations so that any
2232
+ * Ruby exceptions (via longjmp) cannot bypass C++ destructors and leak
2233
+ * memory, and later coercions cannot mutate earlier strings.
2234
+ */
2235
+ StringValue(str);
2236
+ str = rb_str_new_frozen(str);
2237
+ if (rb_obj_is_kind_of(pattern, re2_cRegexp)) {
2238
+ p = unwrap_re2_regexp(pattern);
2239
+ } else {
2240
+ StringValue(pattern);
2241
+ pattern = rb_str_new_frozen(pattern);
2242
+ }
1741
2243
  StringValue(rewrite);
2244
+ rewrite = rb_str_new_frozen(rewrite);
1742
2245
 
1743
2246
  /* Take a copy of str so it can be modified in-place by
1744
2247
  * RE2::GlobalReplace.
1745
2248
  */
1746
- re2_pattern *p;
1747
- StringValue(str);
1748
2249
  std::string str_as_string(RSTRING_PTR(str), RSTRING_LEN(str));
1749
2250
 
1750
- /* Do the replacement. */
1751
- if (rb_obj_is_kind_of(pattern, re2_cRegexp)) {
1752
- TypedData_Get_Struct(pattern, re2_pattern, &re2_regexp_data_type, p);
1753
- RE2::GlobalReplace(&str_as_string, *p->pattern,
1754
- re2::StringPiece(RSTRING_PTR(rewrite), RSTRING_LEN(rewrite)));
2251
+ nogvl_replace_arg arg;
2252
+ arg.str = &str_as_string;
2253
+ if (p) {
2254
+ arg.pattern = p->pattern;
2255
+ } else {
2256
+ arg.pattern = nullptr;
2257
+ arg.string_pattern = re2::StringPiece(
2258
+ RSTRING_PTR(pattern), RSTRING_LEN(pattern));
2259
+ }
2260
+ arg.rewrite = re2::StringPiece(
2261
+ RSTRING_PTR(rewrite), RSTRING_LEN(rewrite));
2262
+
2263
+ #ifdef _WIN32
2264
+ nogvl_global_replace(&arg);
2265
+ #else
2266
+ rb_thread_call_without_gvl(nogvl_global_replace, &arg, NULL, NULL);
2267
+ #endif
1755
2268
 
2269
+ RB_GC_GUARD(rewrite);
2270
+ RB_GC_GUARD(pattern);
2271
+
2272
+ if (p) {
1756
2273
  return encoded_str_new(str_as_string.data(), str_as_string.size(),
1757
2274
  p->pattern->options().encoding());
1758
2275
  } else {
1759
- /* Ensure pattern is a string. */
2276
+ return encoded_str_new(str_as_string.data(), str_as_string.size(),
2277
+ RE2::Options::EncodingUTF8);
2278
+ }
2279
+ }
2280
+
2281
+ /*
2282
+ * If `pattern` matches `text`, returns a copy of `rewrite` with substitutions
2283
+ * using
2284
+ * {https://github.com/google/re2/blob/bc0faab533e2b27b85b8ad312abf061e33ed6b5d/re2/re2.h#L499-L510
2285
+ * `Extract`}. Non-matching portions of `text` are ignored.
2286
+ *
2287
+ * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
2288
+ * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
2289
+ * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
2290
+ *
2291
+ * @param [String] text the string from which to extract
2292
+ * @param [String, RE2::Regexp] pattern a regexp matching the text
2293
+ * @param [String] rewrite the rewrite string with `\1`-style substitutions
2294
+ * @return [String, nil] the extracted string on a successful match or nil if
2295
+ * there is no match
2296
+ * @raise [TypeError] if the given rewrite or pattern (if not provided as a
2297
+ * {RE2::Regexp}) cannot be coerced to `String`s
2298
+ * @example
2299
+ * RE2.extract("alice@example.com", '(\w+)@(\w+)', '\2-\1')
2300
+ * #=> "example-alice"
2301
+ * RE2.extract("no match", '(\d+)', '\1') #=> nil
2302
+ */
2303
+ static VALUE re2_extract(VALUE, VALUE text, VALUE pattern,
2304
+ VALUE rewrite) {
2305
+ re2_pattern *p = nullptr;
2306
+
2307
+ /* Coerce and freeze all arguments before any C++ allocations so that any
2308
+ * Ruby exceptions (via longjmp) cannot bypass C++ destructors and leak
2309
+ * memory, and later coercions cannot mutate earlier strings.
2310
+ */
2311
+ StringValue(text);
2312
+ text = rb_str_new_frozen(text);
2313
+ if (rb_obj_is_kind_of(pattern, re2_cRegexp)) {
2314
+ p = unwrap_re2_regexp(pattern);
2315
+ } else {
1760
2316
  StringValue(pattern);
2317
+ pattern = rb_str_new_frozen(pattern);
2318
+ }
2319
+ StringValue(rewrite);
2320
+ rewrite = rb_str_new_frozen(rewrite);
1761
2321
 
1762
- RE2::GlobalReplace(&str_as_string,
1763
- re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)),
1764
- re2::StringPiece(RSTRING_PTR(rewrite), RSTRING_LEN(rewrite)));
2322
+ std::string out;
1765
2323
 
1766
- return encoded_str_new(str_as_string.data(), str_as_string.size(), RE2::Options::EncodingUTF8);
2324
+ nogvl_extract_arg arg;
2325
+ arg.text = re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text));
2326
+ if (p) {
2327
+ arg.pattern = p->pattern;
2328
+ } else {
2329
+ arg.pattern = nullptr;
2330
+ arg.string_pattern = re2::StringPiece(
2331
+ RSTRING_PTR(pattern), RSTRING_LEN(pattern));
2332
+ }
2333
+ arg.rewrite = re2::StringPiece(
2334
+ RSTRING_PTR(rewrite), RSTRING_LEN(rewrite));
2335
+ arg.out = &out;
2336
+ arg.extracted = false;
2337
+
2338
+ #ifdef _WIN32
2339
+ nogvl_extract(&arg);
2340
+ #else
2341
+ rb_thread_call_without_gvl(nogvl_extract, &arg, NULL, NULL);
2342
+ #endif
2343
+
2344
+ RB_GC_GUARD(text);
2345
+ RB_GC_GUARD(rewrite);
2346
+ RB_GC_GUARD(pattern);
2347
+
2348
+ if (arg.extracted) {
2349
+ return encoded_str_new(out.data(), out.size(),
2350
+ p ? p->pattern->options().encoding()
2351
+ : RE2::Options::EncodingUTF8);
2352
+ } else {
2353
+ return Qnil;
1767
2354
  }
1768
2355
  }
1769
2356
 
@@ -1778,9 +2365,12 @@ static VALUE re2_GlobalReplace(VALUE, VALUE str, VALUE pattern,
1778
2365
  * @raise [TypeError] if the given unquoted string cannot be coerced to a `String`
1779
2366
  * @return [String] the escaped string
1780
2367
  * @example
1781
- * RE2::Regexp.escape("1.5-2.0?") #=> "1\.5\-2\.0\?"
2368
+ * RE2.escape("1.5-2.0?") #=> "1\\.5\\-2\\.0\\?"
2369
+ * RE2.quote("1.5-2.0?") #=> "1\\.5\\-2\\.0\\?"
2370
+ * RE2::Regexp.escape("1.5-2.0?") #=> "1\\.5\\-2\\.0\\?"
2371
+ * RE2::Regexp.quote("1.5-2.0?") #=> "1\\.5\\-2\\.0\\?"
1782
2372
  */
1783
- static VALUE re2_QuoteMeta(VALUE, VALUE unquoted) {
2373
+ static VALUE re2_escape(VALUE, VALUE unquoted) {
1784
2374
  StringValue(unquoted);
1785
2375
 
1786
2376
  std::string quoted_string = RE2::QuoteMeta(
@@ -1790,7 +2380,7 @@ static VALUE re2_QuoteMeta(VALUE, VALUE unquoted) {
1790
2380
  }
1791
2381
 
1792
2382
  static void re2_set_free(void *ptr) {
1793
- re2_set *s = reinterpret_cast<re2_set *>(ptr);
2383
+ re2_set *s = static_cast<re2_set *>(ptr);
1794
2384
  if (s->set) {
1795
2385
  delete s->set;
1796
2386
  }
@@ -1798,7 +2388,7 @@ static void re2_set_free(void *ptr) {
1798
2388
  }
1799
2389
 
1800
2390
  static size_t re2_set_memsize(const void *ptr) {
1801
- const re2_set *s = reinterpret_cast<const re2_set *>(ptr);
2391
+ const re2_set *s = static_cast<const re2_set *>(ptr);
1802
2392
  size_t size = sizeof(*s);
1803
2393
  if (s->set) {
1804
2394
  size += sizeof(*s->set);
@@ -1818,9 +2408,18 @@ static const rb_data_type_t re2_set_data_type = {
1818
2408
  0,
1819
2409
  // IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
1820
2410
  // macro to update VALUE references, as to trigger write barriers.
1821
- RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
2411
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE
1822
2412
  };
1823
2413
 
2414
+ static re2_set *unwrap_re2_set(VALUE self) {
2415
+ re2_set *s;
2416
+ TypedData_Get_Struct(self, re2_set, &re2_set_data_type, s);
2417
+ if (!s->set) {
2418
+ rb_raise(rb_eTypeError, "uninitialized RE2::Set");
2419
+ }
2420
+ return s;
2421
+ }
2422
+
1824
2423
  static VALUE re2_set_allocate(VALUE klass) {
1825
2424
  re2_set *s;
1826
2425
  VALUE result = TypedData_Make_Struct(klass, re2_set, &re2_set_data_type, s);
@@ -1828,6 +2427,10 @@ static VALUE re2_set_allocate(VALUE klass) {
1828
2427
  return result;
1829
2428
  }
1830
2429
 
2430
+ static VALUE re2_set_initialize_copy(VALUE, VALUE) {
2431
+ rb_raise(rb_eTypeError, "cannot copy RE2::Set");
2432
+ }
2433
+
1831
2434
  /*
1832
2435
  * Returns a new {RE2::Set} object, a collection of patterns that can be
1833
2436
  * searched for simultaneously.
@@ -1898,8 +2501,15 @@ static VALUE re2_set_initialize(int argc, VALUE *argv, VALUE self) {
1898
2501
  parse_re2_options(&re2_options, options);
1899
2502
  }
1900
2503
 
2504
+ rb_check_frozen(self);
2505
+
2506
+ if (s->set) {
2507
+ delete s->set;
2508
+ s->set = nullptr;
2509
+ }
2510
+
1901
2511
  s->set = new(std::nothrow) RE2::Set(re2_options, re2_anchor);
1902
- if (s->set == 0) {
2512
+ if (s->set == nullptr) {
1903
2513
  rb_raise(rb_eNoMemError, "not enough memory to allocate RE2::Set object");
1904
2514
  }
1905
2515
 
@@ -1922,24 +2532,22 @@ static VALUE re2_set_initialize(int argc, VALUE *argv, VALUE self) {
1922
2532
  static VALUE re2_set_add(VALUE self, VALUE pattern) {
1923
2533
  StringValue(pattern);
1924
2534
 
1925
- re2_set *s;
1926
- TypedData_Get_Struct(self, re2_set, &re2_set_data_type, s);
2535
+ re2_set *s = unwrap_re2_set(self);
2536
+ rb_check_frozen(self);
1927
2537
 
1928
- /* To prevent the memory of the err string leaking when we call rb_raise,
1929
- * take a copy of it and let it go out of scope.
1930
- */
1931
- char msg[100];
1932
2538
  int index;
2539
+ VALUE msg;
1933
2540
 
1934
2541
  {
1935
2542
  std::string err;
1936
2543
  index = s->set->Add(
1937
2544
  re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)), &err);
1938
- strlcpy(msg, err.c_str(), sizeof(msg));
2545
+ msg = rb_str_new(err.data(), err.size());
1939
2546
  }
1940
2547
 
1941
2548
  if (index < 0) {
1942
- rb_raise(rb_eArgError, "str rejected by RE2::Set->Add(): %s", msg);
2549
+ rb_raise(rb_eArgError,
2550
+ "str rejected by RE2::Set->Add(): %s", RSTRING_PTR(msg));
1943
2551
  }
1944
2552
 
1945
2553
  return INT2FIX(index);
@@ -1956,10 +2564,35 @@ static VALUE re2_set_add(VALUE self, VALUE pattern) {
1956
2564
  * set.compile #=> true
1957
2565
  */
1958
2566
  static VALUE re2_set_compile(VALUE self) {
1959
- re2_set *s;
1960
- TypedData_Get_Struct(self, re2_set, &re2_set_data_type, s);
2567
+ re2_set *s = unwrap_re2_set(self);
2568
+ rb_check_frozen(self);
1961
2569
 
1962
- return BOOL2RUBY(s->set->Compile());
2570
+ bool compiled = s->set->Compile();
2571
+
2572
+ if (compiled) {
2573
+ rb_obj_freeze(self);
2574
+ }
2575
+
2576
+ return BOOL2RUBY(compiled);
2577
+ }
2578
+
2579
+ /*
2580
+ * Returns the size of the {RE2::Set}.
2581
+ *
2582
+ * @return [Integer] the number of patterns in the set
2583
+ * @example
2584
+ * set = RE2::Set.new
2585
+ * set.add("abc")
2586
+ * set.size #=> 1
2587
+ */
2588
+ static VALUE re2_set_size(VALUE self) {
2589
+ #ifdef HAVE_SET_SIZE
2590
+ re2_set *s = unwrap_re2_set(self);
2591
+
2592
+ return INT2FIX(s->set->Size());
2593
+ #else
2594
+ rb_raise(re2_eSetUnsupportedError, "current version of RE2::Set does not have Size method");
2595
+ #endif
1963
2596
  }
1964
2597
 
1965
2598
  /*
@@ -1978,6 +2611,19 @@ static VALUE re2_set_match_raises_errors_p(VALUE) {
1978
2611
  #endif
1979
2612
  }
1980
2613
 
2614
+ /*
2615
+ * Returns whether the underlying RE2 version has a Set::Size method.
2616
+ *
2617
+ * @return [Boolean] whether the underlying RE2 has a Set::Size method
2618
+ */
2619
+ static VALUE re2_set_size_p(VALUE) {
2620
+ #ifdef HAVE_SET_SIZE
2621
+ return Qtrue;
2622
+ #else
2623
+ return Qfalse;
2624
+ #endif
2625
+ }
2626
+
1981
2627
  /*
1982
2628
  * Matches the given text against patterns in the set, returning an array of
1983
2629
  * integer indices of the matching patterns if matched or an empty array if
@@ -2024,8 +2670,9 @@ static VALUE re2_set_match(int argc, VALUE *argv, const VALUE self) {
2024
2670
  rb_scan_args(argc, argv, "11", &str, &options);
2025
2671
 
2026
2672
  StringValue(str);
2027
- re2_set *s;
2028
- TypedData_Get_Struct(self, re2_set, &re2_set_data_type, s);
2673
+ str = rb_str_new_frozen(str);
2674
+
2675
+ re2_set *s = unwrap_re2_set(self);
2029
2676
 
2030
2677
  if (RTEST(options)) {
2031
2678
  Check_Type(options, T_HASH);
@@ -2041,8 +2688,21 @@ static VALUE re2_set_match(int argc, VALUE *argv, const VALUE self) {
2041
2688
  if (raise_exception) {
2042
2689
  #ifdef HAVE_ERROR_INFO_ARGUMENT
2043
2690
  RE2::Set::ErrorInfo e;
2044
- bool match_failed = !s->set->Match(
2045
- re2::StringPiece(RSTRING_PTR(str), RSTRING_LEN(str)), &v, &e);
2691
+ nogvl_set_match_arg arg;
2692
+ arg.set = s->set;
2693
+ arg.text = re2::StringPiece(RSTRING_PTR(str), RSTRING_LEN(str));
2694
+ arg.v = &v;
2695
+ arg.error_info = &e;
2696
+ arg.matched = false;
2697
+
2698
+ #ifdef _WIN32
2699
+ nogvl_set_match(&arg);
2700
+ #else
2701
+ rb_thread_call_without_gvl(nogvl_set_match, &arg, NULL, NULL);
2702
+ #endif
2703
+ RB_GC_GUARD(str);
2704
+
2705
+ bool match_failed = !arg.matched;
2046
2706
  VALUE result = rb_ary_new2(v.size());
2047
2707
 
2048
2708
  if (match_failed) {
@@ -2059,8 +2719,8 @@ static VALUE re2_set_match(int argc, VALUE *argv, const VALUE self) {
2059
2719
  rb_raise(re2_eSetMatchError, "Unknown RE2::Set::ErrorKind: %d", e.kind);
2060
2720
  }
2061
2721
  } else {
2062
- for (std::vector<int>::size_type i = 0; i < v.size(); ++i) {
2063
- rb_ary_push(result, INT2FIX(v[i]));
2722
+ for (int index : v) {
2723
+ rb_ary_push(result, INT2FIX(index));
2064
2724
  }
2065
2725
  }
2066
2726
 
@@ -2069,13 +2729,27 @@ static VALUE re2_set_match(int argc, VALUE *argv, const VALUE self) {
2069
2729
  rb_raise(re2_eSetUnsupportedError, "current version of RE2::Set::Match() does not output error information, :exception option can only be set to false");
2070
2730
  #endif
2071
2731
  } else {
2072
- bool matched = s->set->Match(
2073
- re2::StringPiece(RSTRING_PTR(str), RSTRING_LEN(str)), &v);
2732
+ nogvl_set_match_arg arg;
2733
+ arg.set = s->set;
2734
+ arg.text = re2::StringPiece(RSTRING_PTR(str), RSTRING_LEN(str));
2735
+ arg.v = &v;
2736
+ #ifdef HAVE_ERROR_INFO_ARGUMENT
2737
+ arg.error_info = nullptr;
2738
+ #endif
2739
+ arg.matched = false;
2740
+
2741
+ #ifdef _WIN32
2742
+ nogvl_set_match(&arg);
2743
+ #else
2744
+ rb_thread_call_without_gvl(nogvl_set_match, &arg, NULL, NULL);
2745
+ #endif
2746
+ RB_GC_GUARD(str);
2747
+
2074
2748
  VALUE result = rb_ary_new2(v.size());
2075
2749
 
2076
- if (matched) {
2077
- for (std::vector<int>::size_type i = 0; i < v.size(); ++i) {
2078
- rb_ary_push(result, INT2FIX(v[i]));
2750
+ if (arg.matched) {
2751
+ for (int index : v) {
2752
+ rb_ary_push(result, INT2FIX(index));
2079
2753
  }
2080
2754
  }
2081
2755
 
@@ -2084,6 +2758,8 @@ static VALUE re2_set_match(int argc, VALUE *argv, const VALUE self) {
2084
2758
  }
2085
2759
 
2086
2760
  extern "C" void Init_re2(void) {
2761
+ rb_ext_ractor_safe(true);
2762
+
2087
2763
  re2_mRE2 = rb_define_module("RE2");
2088
2764
  re2_cRegexp = rb_define_class_under(re2_mRE2, "Regexp", rb_cObject);
2089
2765
  re2_eRegexpUnsupportedError = rb_define_class_under(re2_cRegexp,
@@ -2119,6 +2795,14 @@ extern "C" void Init_re2(void) {
2119
2795
  RUBY_METHOD_FUNC(re2_matchdata_begin), 1);
2120
2796
  rb_define_method(re2_cMatchData, "end",
2121
2797
  RUBY_METHOD_FUNC(re2_matchdata_end), 1);
2798
+ rb_define_method(re2_cMatchData, "pre_match",
2799
+ RUBY_METHOD_FUNC(re2_matchdata_pre_match), 0);
2800
+ rb_define_method(re2_cMatchData, "post_match",
2801
+ RUBY_METHOD_FUNC(re2_matchdata_post_match), 0);
2802
+ rb_define_method(re2_cMatchData, "offset",
2803
+ RUBY_METHOD_FUNC(re2_matchdata_offset), 1);
2804
+ rb_define_method(re2_cMatchData, "match_length",
2805
+ RUBY_METHOD_FUNC(re2_matchdata_match_length), 1);
2122
2806
  rb_define_method(re2_cMatchData, "[]", RUBY_METHOD_FUNC(re2_matchdata_aref),
2123
2807
  -1);
2124
2808
  rb_define_method(re2_cMatchData, "to_s",
@@ -2127,8 +2811,18 @@ extern "C" void Init_re2(void) {
2127
2811
  RUBY_METHOD_FUNC(re2_matchdata_inspect), 0);
2128
2812
  rb_define_method(re2_cMatchData, "deconstruct",
2129
2813
  RUBY_METHOD_FUNC(re2_matchdata_deconstruct), 0);
2814
+ rb_define_method(re2_cMatchData, "captures",
2815
+ RUBY_METHOD_FUNC(re2_matchdata_deconstruct), 0);
2816
+ rb_define_method(re2_cMatchData, "named_captures",
2817
+ RUBY_METHOD_FUNC(re2_matchdata_named_captures), -1);
2818
+ rb_define_method(re2_cMatchData, "names",
2819
+ RUBY_METHOD_FUNC(re2_matchdata_names), 0);
2820
+ rb_define_method(re2_cMatchData, "values_at",
2821
+ RUBY_METHOD_FUNC(re2_matchdata_values_at), -1);
2130
2822
  rb_define_method(re2_cMatchData, "deconstruct_keys",
2131
2823
  RUBY_METHOD_FUNC(re2_matchdata_deconstruct_keys), 1);
2824
+ rb_define_method(re2_cMatchData, "initialize_copy",
2825
+ RUBY_METHOD_FUNC(re2_matchdata_initialize_copy), 1);
2132
2826
 
2133
2827
  rb_define_method(re2_cScanner, "string",
2134
2828
  RUBY_METHOD_FUNC(re2_scanner_string), 0);
@@ -2140,11 +2834,15 @@ extern "C" void Init_re2(void) {
2140
2834
  RUBY_METHOD_FUNC(re2_scanner_scan), 0);
2141
2835
  rb_define_method(re2_cScanner, "rewind",
2142
2836
  RUBY_METHOD_FUNC(re2_scanner_rewind), 0);
2837
+ rb_define_method(re2_cScanner, "initialize_copy",
2838
+ RUBY_METHOD_FUNC(re2_scanner_initialize_copy), 1);
2143
2839
 
2144
2840
  rb_define_singleton_method(re2_cRegexp, "match_has_endpos_argument?",
2145
2841
  RUBY_METHOD_FUNC(re2_regexp_match_has_endpos_argument_p), 0);
2146
2842
  rb_define_method(re2_cRegexp, "initialize",
2147
2843
  RUBY_METHOD_FUNC(re2_regexp_initialize), -1);
2844
+ rb_define_method(re2_cRegexp, "initialize_copy",
2845
+ RUBY_METHOD_FUNC(re2_regexp_initialize_copy), 1);
2148
2846
  rb_define_method(re2_cRegexp, "ok?", RUBY_METHOD_FUNC(re2_regexp_ok), 0);
2149
2847
  rb_define_method(re2_cRegexp, "error", RUBY_METHOD_FUNC(re2_regexp_error),
2150
2848
  0);
@@ -2158,6 +2856,10 @@ extern "C" void Init_re2(void) {
2158
2856
  RUBY_METHOD_FUNC(re2_regexp_number_of_capturing_groups), 0);
2159
2857
  rb_define_method(re2_cRegexp, "named_capturing_groups",
2160
2858
  RUBY_METHOD_FUNC(re2_regexp_named_capturing_groups), 0);
2859
+ rb_define_method(re2_cRegexp, "named_captures",
2860
+ RUBY_METHOD_FUNC(re2_regexp_named_capturing_groups), 0);
2861
+ rb_define_method(re2_cRegexp, "names",
2862
+ RUBY_METHOD_FUNC(re2_regexp_names), 0);
2161
2863
  rb_define_method(re2_cRegexp, "match", RUBY_METHOD_FUNC(re2_regexp_match),
2162
2864
  -1);
2163
2865
  rb_define_method(re2_cRegexp, "match?", RUBY_METHOD_FUNC(re2_regexp_match_p),
@@ -2208,22 +2910,38 @@ extern "C" void Init_re2(void) {
2208
2910
 
2209
2911
  rb_define_singleton_method(re2_cSet, "match_raises_errors?",
2210
2912
  RUBY_METHOD_FUNC(re2_set_match_raises_errors_p), 0);
2913
+ rb_define_singleton_method(re2_cSet, "size?",
2914
+ RUBY_METHOD_FUNC(re2_set_size_p), 0);
2211
2915
  rb_define_method(re2_cSet, "initialize",
2212
2916
  RUBY_METHOD_FUNC(re2_set_initialize), -1);
2917
+ rb_define_method(re2_cSet, "initialize_copy",
2918
+ RUBY_METHOD_FUNC(re2_set_initialize_copy), 1);
2213
2919
  rb_define_method(re2_cSet, "add", RUBY_METHOD_FUNC(re2_set_add), 1);
2214
2920
  rb_define_method(re2_cSet, "compile", RUBY_METHOD_FUNC(re2_set_compile), 0);
2215
2921
  rb_define_method(re2_cSet, "match", RUBY_METHOD_FUNC(re2_set_match), -1);
2922
+ rb_define_method(re2_cSet, "size", RUBY_METHOD_FUNC(re2_set_size), 0);
2923
+ rb_define_method(re2_cSet, "length", RUBY_METHOD_FUNC(re2_set_size), 0);
2216
2924
 
2925
+ rb_define_module_function(re2_mRE2, "replace",
2926
+ RUBY_METHOD_FUNC(re2_replace), 3);
2217
2927
  rb_define_module_function(re2_mRE2, "Replace",
2218
- RUBY_METHOD_FUNC(re2_Replace), 3);
2928
+ RUBY_METHOD_FUNC(re2_replace), 3);
2929
+ rb_define_module_function(re2_mRE2, "global_replace",
2930
+ RUBY_METHOD_FUNC(re2_global_replace), 3);
2219
2931
  rb_define_module_function(re2_mRE2, "GlobalReplace",
2220
- RUBY_METHOD_FUNC(re2_GlobalReplace), 3);
2932
+ RUBY_METHOD_FUNC(re2_global_replace), 3);
2933
+ rb_define_module_function(re2_mRE2, "extract",
2934
+ RUBY_METHOD_FUNC(re2_extract), 3);
2221
2935
  rb_define_module_function(re2_mRE2, "QuoteMeta",
2222
- RUBY_METHOD_FUNC(re2_QuoteMeta), 1);
2936
+ RUBY_METHOD_FUNC(re2_escape), 1);
2937
+ rb_define_module_function(re2_mRE2, "escape",
2938
+ RUBY_METHOD_FUNC(re2_escape), 1);
2939
+ rb_define_module_function(re2_mRE2, "quote",
2940
+ RUBY_METHOD_FUNC(re2_escape), 1);
2223
2941
  rb_define_singleton_method(re2_cRegexp, "escape",
2224
- RUBY_METHOD_FUNC(re2_QuoteMeta), 1);
2942
+ RUBY_METHOD_FUNC(re2_escape), 1);
2225
2943
  rb_define_singleton_method(re2_cRegexp, "quote",
2226
- RUBY_METHOD_FUNC(re2_QuoteMeta), 1);
2944
+ RUBY_METHOD_FUNC(re2_escape), 1);
2227
2945
 
2228
2946
  // (see RE2::Regexp#initialize)
2229
2947
  rb_define_singleton_method(re2_cRegexp, "compile",
@@ -2251,4 +2969,5 @@ extern "C" void Init_re2(void) {
2251
2969
  id_submatches = rb_intern("submatches");
2252
2970
  id_startpos = rb_intern("startpos");
2253
2971
  id_endpos = rb_intern("endpos");
2972
+ id_symbolize_names = rb_intern("symbolize_names");
2254
2973
  }