re2 2.27.0 → 2.29.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.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/README.md +11 -1
- data/ext/re2/extconf.rb +2 -26
- data/ext/re2/re2.cc +175 -75
- data/lib/re2/match_data.rb +47 -0
- data/lib/re2/regexp.rb +43 -0
- data/lib/re2/version.rb +1 -1
- data/lib/re2.rb +1 -0
- data/re2.gemspec +2 -1
- data/spec/re2/match_data_spec.rb +283 -2
- data/spec/re2/regexp_spec.rb +175 -26
- data/spec/re2/scanner_spec.rb +9 -0
- data/spec/re2/set_spec.rb +31 -0
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dec6d6cc840ab3dd3c31626f4bfbcc1d4f55fe9d537d7e6196afe6b3e6a3a0ee
|
|
4
|
+
data.tar.gz: a06cd946502d973e2cb9f0003f22958dadc588a8b18079d4f0d660520ff93cd1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d5ddc945b8c92f9c61c0c8e73974307b908c7da6bb535508733eccb8659f04005150c7d068b6689ef7b75c15c11f81703615a43e8fbfb0b28ddf6ecb593fb971
|
|
7
|
+
data.tar.gz: 6b889625aa01b8e53e5ac7b2d0b975b8406cc8013005ba3231086832bf5e8f65e40f321b75f426b54ee37c7de72877a161aeacd9ae0b99a2a5e7e3dac9b26b97
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Python".
|
|
|
6
6
|
|
|
7
7
|
[](https://github.com/mudge/re2/actions)
|
|
8
8
|
|
|
9
|
-
**Current version:** 2.
|
|
9
|
+
**Current version:** 2.29.0
|
|
10
10
|
**Bundled RE2 version:** libre2.11 (2025-11-05)
|
|
11
11
|
|
|
12
12
|
```ruby
|
|
@@ -232,6 +232,14 @@ m.offset(1) #=> [4, 7]
|
|
|
232
232
|
m.match_length(1) #=> 3
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
+
As RE2 works in bytes, the [`byteoffset`](https://mudge.name/re2/RE2/MatchData.html#byteoffset-instance_method), [`bytebegin`](https://mudge.name/re2/RE2/MatchData.html#bytebegin-instance_method) and [`byteend`](https://mudge.name/re2/RE2/MatchData.html#byteend-instance_method) of a match are cheaper to retrieve than their character equivalents:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
m = RE2::Regexp.new('(Ruby)').partial_match("I ♥ Ruby")
|
|
239
|
+
m.offset(1) #=> [4, 8]
|
|
240
|
+
m.byteoffset(1) #=> [6, 10]
|
|
241
|
+
```
|
|
242
|
+
|
|
235
243
|
`RE2::MatchData` objects can also be used with Ruby's [pattern matching](https://docs.ruby-lang.org/en/3.2/syntax/pattern_matching_rdoc.html):
|
|
236
244
|
|
|
237
245
|
```ruby
|
|
@@ -483,6 +491,8 @@ Alternatively, you can set the `RE2_USE_SYSTEM_LIBRARIES` environment variable i
|
|
|
483
491
|
when passing strings with null bytes.
|
|
484
492
|
* Thanks to [Maciej Gajewski](https://github.com/konieczkow) for helping
|
|
485
493
|
confirm issues with GC compaction and mutable strings.
|
|
494
|
+
* Thanks to [Konstantin Haase](https://github.com/rkh) for reporting that
|
|
495
|
+
`RE2::Regexp` shouldn't implement `to_str`.
|
|
486
496
|
|
|
487
497
|
## Contact
|
|
488
498
|
|
data/ext/re2/extconf.rb
CHANGED
|
@@ -132,38 +132,14 @@ module RE2
|
|
|
132
132
|
|
|
133
133
|
def build_extension
|
|
134
134
|
$CFLAGS << " -Wall -Wextra -funroll-loops"
|
|
135
|
-
$CXXFLAGS << " -Wall -Wextra -funroll-loops"
|
|
135
|
+
$CXXFLAGS << " -Wall -Wextra -funroll-loops -std=c++17"
|
|
136
136
|
|
|
137
137
|
# Pass -x c++ to force gcc to compile the test program
|
|
138
138
|
# as C++ (as it will end in .c by default).
|
|
139
|
-
compile_options = +"-x c++"
|
|
139
|
+
compile_options = +"-x c++ -std=c++17"
|
|
140
140
|
|
|
141
141
|
have_library("stdc++")
|
|
142
142
|
|
|
143
|
-
minimal_program = <<~SRC
|
|
144
|
-
#include <re2/re2.h>
|
|
145
|
-
int main() { return 0; }
|
|
146
|
-
SRC
|
|
147
|
-
|
|
148
|
-
re2_requires_version_flag = checking_for("re2 that requires explicit C++ version flag") do
|
|
149
|
-
!try_compile(minimal_program, compile_options)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
if re2_requires_version_flag
|
|
153
|
-
# Recent versions of RE2 depend directly on Abseil, which requires a
|
|
154
|
-
# compiler with C++17 support.
|
|
155
|
-
abort "Cannot compile re2 with your compiler: recent versions require C++17 support." unless %w[c++20 c++17 c++11].any? do |std|
|
|
156
|
-
checking_for("re2 that compiles with #{std} standard") do
|
|
157
|
-
if try_compile(minimal_program, compile_options + " -std=#{std}")
|
|
158
|
-
compile_options << " -std=#{std}"
|
|
159
|
-
$CPPFLAGS << " -std=#{std}"
|
|
160
|
-
|
|
161
|
-
true
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
143
|
# Determine which version of re2 the user has installed.
|
|
168
144
|
# Revision d9f8806c004d added an `endpos` argument to the
|
|
169
145
|
# generic Match() function.
|
data/ext/re2/re2.cc
CHANGED
|
@@ -42,6 +42,7 @@ typedef struct {
|
|
|
42
42
|
|
|
43
43
|
typedef struct {
|
|
44
44
|
RE2::Set *set;
|
|
45
|
+
bool compiled;
|
|
45
46
|
} re2_set;
|
|
46
47
|
|
|
47
48
|
struct nogvl_match_arg {
|
|
@@ -170,6 +171,13 @@ static void *nogvl_extract(void *ptr) {
|
|
|
170
171
|
return nullptr;
|
|
171
172
|
}
|
|
172
173
|
|
|
174
|
+
static std::string group_name(VALUE key) {
|
|
175
|
+
VALUE str = SYMBOL_P(key) ? rb_sym2str(key) : key;
|
|
176
|
+
str = rb_str_conv_enc(str, rb_enc_get(str), rb_utf8_encoding());
|
|
177
|
+
|
|
178
|
+
return std::string(RSTRING_PTR(str), RSTRING_LEN(str));
|
|
179
|
+
}
|
|
180
|
+
|
|
173
181
|
VALUE re2_mRE2, re2_cRegexp, re2_cMatchData, re2_cScanner, re2_cSet,
|
|
174
182
|
re2_eSetMatchError, re2_eSetUnsupportedError, re2_eRegexpUnsupportedError;
|
|
175
183
|
|
|
@@ -412,9 +420,9 @@ static re2_scanner *unwrap_re2_scanner(VALUE self) {
|
|
|
412
420
|
* in alphabetical order rather than definition order, as RE2 stores named
|
|
413
421
|
* groups internally in a sorted map.
|
|
414
422
|
*
|
|
415
|
-
* Note RE2
|
|
416
|
-
*
|
|
417
|
-
*
|
|
423
|
+
* Note RE2 stores capturing group names as UTF-8, even if the {RE2::Regexp}
|
|
424
|
+
* sets the `:utf8` option to false so strings will always be returned in
|
|
425
|
+
* UTF-8.
|
|
418
426
|
*
|
|
419
427
|
* @return [Array<String>] an array of names of named capturing groups
|
|
420
428
|
* @example
|
|
@@ -427,9 +435,7 @@ static VALUE re2_regexp_names(const VALUE self) {
|
|
|
427
435
|
VALUE names = rb_ary_new2(groups.size());
|
|
428
436
|
|
|
429
437
|
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()));
|
|
438
|
+
rb_ary_push(names, rb_utf8_str_new(group.first.data(), group.first.size()));
|
|
433
439
|
}
|
|
434
440
|
|
|
435
441
|
return names;
|
|
@@ -523,6 +529,8 @@ static VALUE re2_scanner_rewind(VALUE self) {
|
|
|
523
529
|
}
|
|
524
530
|
|
|
525
531
|
static VALUE re2_scanner_initialize_copy(VALUE self, VALUE other) {
|
|
532
|
+
if (self == other) return self;
|
|
533
|
+
|
|
526
534
|
re2_scanner *self_c;
|
|
527
535
|
re2_scanner *other_c = unwrap_re2_scanner(other);
|
|
528
536
|
|
|
@@ -646,20 +654,13 @@ static re2::StringPiece *re2_matchdata_find_match(VALUE idx, const VALUE self) {
|
|
|
646
654
|
|
|
647
655
|
if (RB_INTEGER_TYPE_P(idx)) {
|
|
648
656
|
id = NUM2INT(idx);
|
|
649
|
-
} else if (SYMBOL_P(idx)) {
|
|
650
|
-
const auto& groups = p->pattern->NamedCapturingGroups();
|
|
651
|
-
auto search = groups.find(rb_id2name(SYM2ID(idx)));
|
|
652
|
-
|
|
653
|
-
if (search != groups.end()) {
|
|
654
|
-
id = search->second;
|
|
655
|
-
} else {
|
|
656
|
-
return nullptr;
|
|
657
|
-
}
|
|
658
657
|
} else {
|
|
659
|
-
|
|
658
|
+
if (!SYMBOL_P(idx)) {
|
|
659
|
+
StringValue(idx);
|
|
660
|
+
}
|
|
660
661
|
|
|
661
662
|
const auto& groups = p->pattern->NamedCapturingGroups();
|
|
662
|
-
auto search = groups.find(
|
|
663
|
+
auto search = groups.find(group_name(idx));
|
|
663
664
|
|
|
664
665
|
if (search != groups.end()) {
|
|
665
666
|
id = search->second;
|
|
@@ -712,11 +713,11 @@ static VALUE re2_matchdata_begin(const VALUE self, VALUE n) {
|
|
|
712
713
|
re2::StringPiece *match = re2_matchdata_find_match(n, self);
|
|
713
714
|
if (match == nullptr) {
|
|
714
715
|
return Qnil;
|
|
715
|
-
} else {
|
|
716
|
-
long offset = match->data() - RSTRING_PTR(m->text);
|
|
717
|
-
|
|
718
|
-
return LONG2NUM(rb_str_sublen(m->text, offset));
|
|
719
716
|
}
|
|
717
|
+
|
|
718
|
+
long offset = match->data() - RSTRING_PTR(m->text);
|
|
719
|
+
|
|
720
|
+
return LONG2NUM(rb_str_sublen(m->text, offset));
|
|
720
721
|
}
|
|
721
722
|
|
|
722
723
|
/*
|
|
@@ -737,11 +738,11 @@ static VALUE re2_matchdata_end(const VALUE self, VALUE n) {
|
|
|
737
738
|
re2::StringPiece *match = re2_matchdata_find_match(n, self);
|
|
738
739
|
if (match == nullptr) {
|
|
739
740
|
return Qnil;
|
|
740
|
-
} else {
|
|
741
|
-
long offset = (match->data() - RSTRING_PTR(m->text)) + match->size();
|
|
742
|
-
|
|
743
|
-
return LONG2NUM(rb_str_sublen(m->text, offset));
|
|
744
741
|
}
|
|
742
|
+
|
|
743
|
+
long offset = (match->data() - RSTRING_PTR(m->text)) + match->size();
|
|
744
|
+
|
|
745
|
+
return LONG2NUM(rb_str_sublen(m->text, offset));
|
|
745
746
|
}
|
|
746
747
|
|
|
747
748
|
/*
|
|
@@ -829,6 +830,85 @@ static VALUE re2_matchdata_offset(const VALUE self, VALUE n) {
|
|
|
829
830
|
return array;
|
|
830
831
|
}
|
|
831
832
|
|
|
833
|
+
/*
|
|
834
|
+
* Returns the byte offset of the start of the nth element of the
|
|
835
|
+
* {RE2::MatchData}, like Ruby's `MatchData#bytebegin`. This is cheaper than
|
|
836
|
+
* {RE2::MatchData#begin} as it does not need to count characters.
|
|
837
|
+
*
|
|
838
|
+
* @param [Integer, String, Symbol] n the name or number of the submatch
|
|
839
|
+
* @return [Integer, nil] the byte offset of the start of the match or `nil` if
|
|
840
|
+
* there is no such submatch
|
|
841
|
+
* @example
|
|
842
|
+
* m = RE2::Regexp.new('(Ruby)').partial_match("I ♥ Ruby")
|
|
843
|
+
* m.bytebegin(0) #=> 6
|
|
844
|
+
* m.begin(0) #=> 4
|
|
845
|
+
*/
|
|
846
|
+
static VALUE re2_matchdata_bytebegin(const VALUE self, VALUE n) {
|
|
847
|
+
re2_matchdata *m = unwrap_re2_matchdata(self);
|
|
848
|
+
|
|
849
|
+
re2::StringPiece *match = re2_matchdata_find_match(n, self);
|
|
850
|
+
if (match == nullptr) {
|
|
851
|
+
return Qnil;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
return LONG2NUM(match->data() - RSTRING_PTR(m->text));
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/*
|
|
858
|
+
* Returns the byte offset of the byte following the end of the nth element of
|
|
859
|
+
* the {RE2::MatchData}, like Ruby's `MatchData#byteend`. This is cheaper than
|
|
860
|
+
* {RE2::MatchData#end} as it does not need to count characters.
|
|
861
|
+
*
|
|
862
|
+
* @param [Integer, String, Symbol] n the name or number of the match
|
|
863
|
+
* @return [Integer, nil] the byte offset of the byte following the end of the
|
|
864
|
+
* match or `nil` if there is no such match
|
|
865
|
+
* @example
|
|
866
|
+
* m = RE2::Regexp.new('(Ruby)').partial_match("I ♥ Ruby")
|
|
867
|
+
* m.byteend(0) #=> 10
|
|
868
|
+
* m.end(0) #=> 8
|
|
869
|
+
*/
|
|
870
|
+
static VALUE re2_matchdata_byteend(const VALUE self, VALUE n) {
|
|
871
|
+
re2_matchdata *m = unwrap_re2_matchdata(self);
|
|
872
|
+
|
|
873
|
+
re2::StringPiece *match = re2_matchdata_find_match(n, self);
|
|
874
|
+
if (match == nullptr) {
|
|
875
|
+
return Qnil;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return LONG2NUM((match->data() - RSTRING_PTR(m->text)) + match->size());
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/*
|
|
882
|
+
* Returns a two-element array containing the beginning and ending byte
|
|
883
|
+
* offsets of the nth match, like Ruby's `MatchData#byteoffset`. This is
|
|
884
|
+
* cheaper than {RE2::MatchData#offset} as it does not need to count
|
|
885
|
+
* characters.
|
|
886
|
+
*
|
|
887
|
+
* @param [Integer, String, Symbol] n the name or number of the match
|
|
888
|
+
* @return [Array<Integer>, nil] a two-element array with the beginning and
|
|
889
|
+
* ending byte offsets of the match or `nil` if there is no such match
|
|
890
|
+
* @example
|
|
891
|
+
* m = RE2::Regexp.new('(Ruby)').partial_match("I ♥ Ruby")
|
|
892
|
+
* m.byteoffset(0) #=> [6, 10]
|
|
893
|
+
* m.offset(0) #=> [4, 8]
|
|
894
|
+
*/
|
|
895
|
+
static VALUE re2_matchdata_byteoffset(const VALUE self, VALUE n) {
|
|
896
|
+
re2_matchdata *m = unwrap_re2_matchdata(self);
|
|
897
|
+
|
|
898
|
+
re2::StringPiece *match = re2_matchdata_find_match(n, self);
|
|
899
|
+
if (match == nullptr) {
|
|
900
|
+
return Qnil;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
long start = match->data() - RSTRING_PTR(m->text);
|
|
904
|
+
|
|
905
|
+
VALUE array = rb_ary_new2(2);
|
|
906
|
+
rb_ary_push(array, LONG2NUM(start));
|
|
907
|
+
rb_ary_push(array, LONG2NUM(start + match->size()));
|
|
908
|
+
|
|
909
|
+
return array;
|
|
910
|
+
}
|
|
911
|
+
|
|
832
912
|
/*
|
|
833
913
|
* Returns the length of the nth match in characters. This is equivalent to
|
|
834
914
|
* `m[n].length` but without allocating a new string.
|
|
@@ -1004,11 +1084,8 @@ static VALUE re2_matchdata_aref(int argc, VALUE *argv, const VALUE self) {
|
|
|
1004
1084
|
VALUE idx, rest;
|
|
1005
1085
|
rb_scan_args(argc, argv, "11", &idx, &rest);
|
|
1006
1086
|
|
|
1007
|
-
if (TYPE(idx) == T_STRING) {
|
|
1008
|
-
return re2_matchdata_named_match(
|
|
1009
|
-
std::string(RSTRING_PTR(idx), RSTRING_LEN(idx)), self);
|
|
1010
|
-
} else if (SYMBOL_P(idx)) {
|
|
1011
|
-
return re2_matchdata_named_match(rb_id2name(SYM2ID(idx)), self);
|
|
1087
|
+
if (TYPE(idx) == T_STRING || SYMBOL_P(idx)) {
|
|
1088
|
+
return re2_matchdata_named_match(group_name(idx), self);
|
|
1012
1089
|
} else if (!NIL_P(rest) || !RB_INTEGER_TYPE_P(idx) || NUM2INT(idx) < 0) {
|
|
1013
1090
|
return rb_ary_aref(argc, argv, re2_matchdata_to_a(self));
|
|
1014
1091
|
} else {
|
|
@@ -1122,6 +1199,7 @@ static VALUE re2_matchdata_deconstruct(const VALUE self) {
|
|
|
1122
1199
|
* Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
|
|
1123
1200
|
* returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
|
|
1124
1201
|
* {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
|
|
1202
|
+
* Capturing group names will always be returned in UTF-8.
|
|
1125
1203
|
*
|
|
1126
1204
|
* @return [Hash] a hash of capturing group names to submatches
|
|
1127
1205
|
* @param [Array<Symbol>, nil] keys an array of `Symbol` capturing group names
|
|
@@ -1151,7 +1229,7 @@ static VALUE re2_matchdata_deconstruct_keys(const VALUE self, const VALUE keys)
|
|
|
1151
1229
|
if (NIL_P(keys)) {
|
|
1152
1230
|
for (const auto& group : groups) {
|
|
1153
1231
|
rb_hash_aset(capturing_groups,
|
|
1154
|
-
|
|
1232
|
+
rb_str_intern(rb_utf8_str_new(group.first.data(), group.first.size())),
|
|
1155
1233
|
re2_matchdata_nth_match(group.second, self));
|
|
1156
1234
|
}
|
|
1157
1235
|
} else {
|
|
@@ -1161,8 +1239,7 @@ static VALUE re2_matchdata_deconstruct_keys(const VALUE self, const VALUE keys)
|
|
|
1161
1239
|
for (int i = 0; i < RARRAY_LEN(keys); ++i) {
|
|
1162
1240
|
VALUE key = rb_ary_entry(keys, i);
|
|
1163
1241
|
Check_Type(key, T_SYMBOL);
|
|
1164
|
-
|
|
1165
|
-
auto search = groups.find(name);
|
|
1242
|
+
auto search = groups.find(group_name(key));
|
|
1166
1243
|
|
|
1167
1244
|
if (search != groups.end()) {
|
|
1168
1245
|
rb_hash_aset(capturing_groups, key, re2_matchdata_nth_match(search->second, self));
|
|
@@ -1182,6 +1259,7 @@ static VALUE re2_matchdata_deconstruct_keys(const VALUE self, const VALUE keys)
|
|
|
1182
1259
|
* Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
|
|
1183
1260
|
* returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
|
|
1184
1261
|
* {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
|
|
1262
|
+
* Capturing group names will always be returned in UTF-8.
|
|
1185
1263
|
*
|
|
1186
1264
|
* @overload named_captures
|
|
1187
1265
|
* Returns a hash with string keys.
|
|
@@ -1219,12 +1297,9 @@ static VALUE re2_matchdata_named_captures(int argc, VALUE *argv, const VALUE sel
|
|
|
1219
1297
|
VALUE result = rb_hash_new();
|
|
1220
1298
|
|
|
1221
1299
|
for (const auto& group : groups) {
|
|
1222
|
-
VALUE key;
|
|
1300
|
+
VALUE key = rb_utf8_str_new(group.first.data(), group.first.size());
|
|
1223
1301
|
if (symbolize) {
|
|
1224
|
-
key =
|
|
1225
|
-
} else {
|
|
1226
|
-
key = encoded_str_new(group.first.data(), group.first.size(),
|
|
1227
|
-
p->pattern->options().encoding());
|
|
1302
|
+
key = rb_str_intern(key);
|
|
1228
1303
|
}
|
|
1229
1304
|
rb_hash_aset(result, key, re2_matchdata_nth_match(group.second, self));
|
|
1230
1305
|
}
|
|
@@ -1237,9 +1312,9 @@ static VALUE re2_matchdata_named_captures(int argc, VALUE *argv, const VALUE sel
|
|
|
1237
1312
|
* alphabetical order rather than definition order, as RE2 stores named groups
|
|
1238
1313
|
* internally in a sorted map.
|
|
1239
1314
|
*
|
|
1240
|
-
* Note RE2
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1315
|
+
* Note RE2 stores capturing group names as UTF-8, even if the {RE2::Regexp}
|
|
1316
|
+
* sets the `:utf8` option to false so strings will always be returned in
|
|
1317
|
+
* UTF-8.
|
|
1243
1318
|
*
|
|
1244
1319
|
* @return [Array<String>] an array of names of named capturing groups
|
|
1245
1320
|
* @example
|
|
@@ -1276,12 +1351,8 @@ static VALUE re2_matchdata_values_at(int argc, VALUE *argv, const VALUE self) {
|
|
|
1276
1351
|
for (int i = 0; i < argc; ++i) {
|
|
1277
1352
|
VALUE idx = argv[i];
|
|
1278
1353
|
|
|
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));
|
|
1354
|
+
if (TYPE(idx) == T_STRING || SYMBOL_P(idx)) {
|
|
1355
|
+
rb_ary_push(result, re2_matchdata_named_match(group_name(idx), self));
|
|
1285
1356
|
} else {
|
|
1286
1357
|
rb_ary_push(result, re2_matchdata_nth_match(NUM2INT(idx), self));
|
|
1287
1358
|
}
|
|
@@ -1291,6 +1362,8 @@ static VALUE re2_matchdata_values_at(int argc, VALUE *argv, const VALUE self) {
|
|
|
1291
1362
|
}
|
|
1292
1363
|
|
|
1293
1364
|
static VALUE re2_matchdata_initialize_copy(VALUE self, VALUE other) {
|
|
1365
|
+
if (self == other) return self;
|
|
1366
|
+
|
|
1294
1367
|
re2_matchdata *self_m;
|
|
1295
1368
|
re2_matchdata *other_m = unwrap_re2_matchdata(other);
|
|
1296
1369
|
|
|
@@ -1376,24 +1449,17 @@ static VALUE re2_regexp_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
1376
1449
|
|
|
1377
1450
|
TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
|
|
1378
1451
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
if (p->pattern) {
|
|
1382
|
-
delete p->pattern;
|
|
1383
|
-
p->pattern = nullptr;
|
|
1384
|
-
}
|
|
1452
|
+
RE2::Options re2_options;
|
|
1385
1453
|
|
|
1386
1454
|
if (RTEST(options)) {
|
|
1387
|
-
RE2::Options re2_options;
|
|
1388
1455
|
parse_re2_options(&re2_options, options);
|
|
1389
|
-
|
|
1390
|
-
p->pattern = new(std::nothrow) RE2(
|
|
1391
|
-
re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)), re2_options);
|
|
1392
|
-
} else {
|
|
1393
|
-
p->pattern = new(std::nothrow) RE2(
|
|
1394
|
-
re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)));
|
|
1395
1456
|
}
|
|
1396
1457
|
|
|
1458
|
+
rb_check_frozen(self);
|
|
1459
|
+
|
|
1460
|
+
p->pattern = new(std::nothrow) RE2(
|
|
1461
|
+
re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)), re2_options);
|
|
1462
|
+
|
|
1397
1463
|
if (p->pattern == nullptr) {
|
|
1398
1464
|
rb_raise(rb_eNoMemError, "not enough memory to allocate RE2 object");
|
|
1399
1465
|
}
|
|
@@ -1778,9 +1844,9 @@ static VALUE re2_regexp_number_of_capturing_groups(const VALUE self) {
|
|
|
1778
1844
|
/*
|
|
1779
1845
|
* Returns a hash of names to capturing indices of groups.
|
|
1780
1846
|
*
|
|
1781
|
-
* Note RE2
|
|
1782
|
-
*
|
|
1783
|
-
*
|
|
1847
|
+
* Note RE2 stores capturing group names as UTF-8, even if the {RE2::Regexp}
|
|
1848
|
+
* sets the `:utf8` option to false so strings will always be returned in
|
|
1849
|
+
* UTF-8.
|
|
1784
1850
|
*
|
|
1785
1851
|
* @return [Hash] a hash of names to capturing indices
|
|
1786
1852
|
*/
|
|
@@ -1791,8 +1857,7 @@ static VALUE re2_regexp_named_capturing_groups(const VALUE self) {
|
|
|
1791
1857
|
|
|
1792
1858
|
for (const auto& group : groups) {
|
|
1793
1859
|
rb_hash_aset(capturing_groups,
|
|
1794
|
-
|
|
1795
|
-
p->pattern->options().encoding()),
|
|
1860
|
+
rb_utf8_str_new(group.first.data(), group.first.size()),
|
|
1796
1861
|
INT2FIX(group.second));
|
|
1797
1862
|
}
|
|
1798
1863
|
|
|
@@ -1818,7 +1883,7 @@ static VALUE re2_regexp_named_capturing_groups(const VALUE self) {
|
|
|
1818
1883
|
* Returns either `true` or `false` indicating whether a successful match was
|
|
1819
1884
|
* made if the pattern contains no capturing groups.
|
|
1820
1885
|
*
|
|
1821
|
-
* @param [String] text the text to search
|
|
1886
|
+
* @param [String, Symbol, nil] text the text to search; `nil` returns `nil`
|
|
1822
1887
|
* @return [RE2::MatchData, nil] if the pattern contains capturing groups
|
|
1823
1888
|
* @return [Boolean] if the pattern does not contain capturing groups
|
|
1824
1889
|
* @raise [NoMemoryError] if there was not enough memory to allocate the submatches
|
|
@@ -1836,7 +1901,7 @@ static VALUE re2_regexp_named_capturing_groups(const VALUE self) {
|
|
|
1836
1901
|
* specific number of submatches to extract (padded with `nil`s if
|
|
1837
1902
|
* necessary).
|
|
1838
1903
|
*
|
|
1839
|
-
* @param [String] text the text to search
|
|
1904
|
+
* @param [String, Symbol, nil] text the text to search; `nil` returns `nil`
|
|
1840
1905
|
* @param [Hash] options the options with which to perform the match
|
|
1841
1906
|
* @option options [Integer] :startpos (0) offset at which to start matching
|
|
1842
1907
|
* @option options [Integer] :endpos offset at which to stop matching, defaults to the text length
|
|
@@ -1869,7 +1934,7 @@ static VALUE re2_regexp_named_capturing_groups(const VALUE self) {
|
|
|
1869
1934
|
* @deprecated Legacy syntax for matching against `text` with a specific
|
|
1870
1935
|
* number of submatches to extract. Use `match(text, submatches: n)` instead.
|
|
1871
1936
|
*
|
|
1872
|
-
* @param [String] text the text to search
|
|
1937
|
+
* @param [String, Symbol, nil] text the text to search; `nil` returns `nil`
|
|
1873
1938
|
* @param [Integer] submatches the number of submatches to extract
|
|
1874
1939
|
* @return [RE2::MatchData, nil] if extracting any submatches
|
|
1875
1940
|
* @return [Boolean] if not extracting any submatches
|
|
@@ -1888,6 +1953,13 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
|
|
|
1888
1953
|
|
|
1889
1954
|
rb_scan_args(argc, argv, "11", &text, &options);
|
|
1890
1955
|
|
|
1956
|
+
if (NIL_P(text)) {
|
|
1957
|
+
return Qnil;
|
|
1958
|
+
}
|
|
1959
|
+
if (SYMBOL_P(text)) {
|
|
1960
|
+
text = rb_sym2str(text);
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1891
1963
|
/* Coerce and freeze text to prevent mutation. */
|
|
1892
1964
|
StringValue(text);
|
|
1893
1965
|
text = rb_str_new_frozen(text);
|
|
@@ -2034,11 +2106,17 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
|
|
|
2034
2106
|
* {https://github.com/google/re2/blob/bc0faab533e2b27b85b8ad312abf061e33ed6b5d/re2/re2.h#L413-L427
|
|
2035
2107
|
* `PartialMatch`}.
|
|
2036
2108
|
*
|
|
2037
|
-
* @param [String] text the text to search
|
|
2109
|
+
* @param [String, Symbol, nil] text the text to search; `nil` returns `false`
|
|
2038
2110
|
* @return [Boolean] whether the match was successful
|
|
2039
2111
|
* @raise [TypeError] if text cannot be coerced to a `String`
|
|
2040
2112
|
*/
|
|
2041
2113
|
static VALUE re2_regexp_match_p(const VALUE self, VALUE text) {
|
|
2114
|
+
if (NIL_P(text)) {
|
|
2115
|
+
return Qfalse;
|
|
2116
|
+
}
|
|
2117
|
+
if (SYMBOL_P(text)) {
|
|
2118
|
+
text = rb_sym2str(text);
|
|
2119
|
+
}
|
|
2042
2120
|
StringValue(text);
|
|
2043
2121
|
text = rb_str_new_frozen(text);
|
|
2044
2122
|
|
|
@@ -2055,11 +2133,17 @@ static VALUE re2_regexp_match_p(const VALUE self, VALUE text) {
|
|
|
2055
2133
|
* {https://github.com/google/re2/blob/bc0faab533e2b27b85b8ad312abf061e33ed6b5d/re2/re2.h#L376-L411
|
|
2056
2134
|
* `FullMatch`}.
|
|
2057
2135
|
*
|
|
2058
|
-
* @param [String] text the text to search
|
|
2136
|
+
* @param [String, Symbol, nil] text the text to search; `nil` returns `false`
|
|
2059
2137
|
* @return [Boolean] whether the match was successful
|
|
2060
2138
|
* @raise [TypeError] if text cannot be coerced to a `String`
|
|
2061
2139
|
*/
|
|
2062
2140
|
static VALUE re2_regexp_full_match_p(const VALUE self, VALUE text) {
|
|
2141
|
+
if (NIL_P(text)) {
|
|
2142
|
+
return Qfalse;
|
|
2143
|
+
}
|
|
2144
|
+
if (SYMBOL_P(text)) {
|
|
2145
|
+
text = rb_sym2str(text);
|
|
2146
|
+
}
|
|
2063
2147
|
StringValue(text);
|
|
2064
2148
|
text = rb_str_new_frozen(text);
|
|
2065
2149
|
|
|
@@ -2503,9 +2587,12 @@ static VALUE re2_set_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
2503
2587
|
|
|
2504
2588
|
rb_check_frozen(self);
|
|
2505
2589
|
|
|
2590
|
+
/* Prevent re-initialisation: #match releases the GVL while holding a
|
|
2591
|
+
* pointer to s->set, so replacing s->set from another thread would free
|
|
2592
|
+
* the object out from under a concurrent match.
|
|
2593
|
+
*/
|
|
2506
2594
|
if (s->set) {
|
|
2507
|
-
|
|
2508
|
-
s->set = nullptr;
|
|
2595
|
+
rb_raise(rb_eTypeError, "already initialized RE2::Set");
|
|
2509
2596
|
}
|
|
2510
2597
|
|
|
2511
2598
|
s->set = new(std::nothrow) RE2::Set(re2_options, re2_anchor);
|
|
@@ -2570,6 +2657,7 @@ static VALUE re2_set_compile(VALUE self) {
|
|
|
2570
2657
|
bool compiled = s->set->Compile();
|
|
2571
2658
|
|
|
2572
2659
|
if (compiled) {
|
|
2660
|
+
s->compiled = true;
|
|
2573
2661
|
rb_obj_freeze(self);
|
|
2574
2662
|
}
|
|
2575
2663
|
|
|
@@ -2687,6 +2775,11 @@ static VALUE re2_set_match(int argc, VALUE *argv, const VALUE self) {
|
|
|
2687
2775
|
|
|
2688
2776
|
if (raise_exception) {
|
|
2689
2777
|
#ifdef HAVE_ERROR_INFO_ARGUMENT
|
|
2778
|
+
/* RE2::Set::Match dereferences a null program after a failed Compile. */
|
|
2779
|
+
if (!s->compiled) {
|
|
2780
|
+
rb_raise(re2_eSetMatchError, "#match must not be called before #compile");
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2690
2783
|
RE2::Set::ErrorInfo e;
|
|
2691
2784
|
nogvl_set_match_arg arg;
|
|
2692
2785
|
arg.set = s->set;
|
|
@@ -2729,6 +2822,10 @@ static VALUE re2_set_match(int argc, VALUE *argv, const VALUE self) {
|
|
|
2729
2822
|
rb_raise(re2_eSetUnsupportedError, "current version of RE2::Set::Match() does not output error information, :exception option can only be set to false");
|
|
2730
2823
|
#endif
|
|
2731
2824
|
} else {
|
|
2825
|
+
if (!s->compiled) {
|
|
2826
|
+
return rb_ary_new();
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2732
2829
|
nogvl_set_match_arg arg;
|
|
2733
2830
|
arg.set = s->set;
|
|
2734
2831
|
arg.text = re2::StringPiece(RSTRING_PTR(str), RSTRING_LEN(str));
|
|
@@ -2801,6 +2898,12 @@ extern "C" void Init_re2(void) {
|
|
|
2801
2898
|
RUBY_METHOD_FUNC(re2_matchdata_post_match), 0);
|
|
2802
2899
|
rb_define_method(re2_cMatchData, "offset",
|
|
2803
2900
|
RUBY_METHOD_FUNC(re2_matchdata_offset), 1);
|
|
2901
|
+
rb_define_method(re2_cMatchData, "bytebegin",
|
|
2902
|
+
RUBY_METHOD_FUNC(re2_matchdata_bytebegin), 1);
|
|
2903
|
+
rb_define_method(re2_cMatchData, "byteend",
|
|
2904
|
+
RUBY_METHOD_FUNC(re2_matchdata_byteend), 1);
|
|
2905
|
+
rb_define_method(re2_cMatchData, "byteoffset",
|
|
2906
|
+
RUBY_METHOD_FUNC(re2_matchdata_byteoffset), 1);
|
|
2804
2907
|
rb_define_method(re2_cMatchData, "match_length",
|
|
2805
2908
|
RUBY_METHOD_FUNC(re2_matchdata_match_length), 1);
|
|
2806
2909
|
rb_define_method(re2_cMatchData, "[]", RUBY_METHOD_FUNC(re2_matchdata_aref),
|
|
@@ -2867,14 +2970,11 @@ extern "C" void Init_re2(void) {
|
|
|
2867
2970
|
rb_define_method(re2_cRegexp, "partial_match?",
|
|
2868
2971
|
RUBY_METHOD_FUNC(re2_regexp_match_p), 1);
|
|
2869
2972
|
rb_define_method(re2_cRegexp, "=~", RUBY_METHOD_FUNC(re2_regexp_match_p), 1);
|
|
2870
|
-
rb_define_method(re2_cRegexp, "===", RUBY_METHOD_FUNC(re2_regexp_match_p), 1);
|
|
2871
2973
|
rb_define_method(re2_cRegexp, "full_match?",
|
|
2872
2974
|
RUBY_METHOD_FUNC(re2_regexp_full_match_p), 1);
|
|
2873
2975
|
rb_define_method(re2_cRegexp, "scan",
|
|
2874
2976
|
RUBY_METHOD_FUNC(re2_regexp_scan), 1);
|
|
2875
2977
|
rb_define_method(re2_cRegexp, "to_s", RUBY_METHOD_FUNC(re2_regexp_to_s), 0);
|
|
2876
|
-
rb_define_method(re2_cRegexp, "to_str", RUBY_METHOD_FUNC(re2_regexp_to_s),
|
|
2877
|
-
0);
|
|
2878
2978
|
rb_define_method(re2_cRegexp, "pattern", RUBY_METHOD_FUNC(re2_regexp_to_s),
|
|
2879
2979
|
0);
|
|
2880
2980
|
rb_define_method(re2_cRegexp, "source", RUBY_METHOD_FUNC(re2_regexp_to_s),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# re2 (https://github.com/mudge/re2)
|
|
4
|
+
# Ruby bindings to RE2, a "fast, safe, thread-friendly alternative to
|
|
5
|
+
# backtracking regular expression engines like those used in PCRE, Perl, and
|
|
6
|
+
# Python".
|
|
7
|
+
#
|
|
8
|
+
# Copyright (c) 2010, Paul Mucur (https://mudge.name)
|
|
9
|
+
# Released under the BSD Licence, please see LICENSE.txt
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
module RE2
|
|
13
|
+
class MatchData
|
|
14
|
+
# Return whether this match has the same regular expression, text, and
|
|
15
|
+
# submatch offsets as another {RE2::MatchData}, like Ruby's own
|
|
16
|
+
# `MatchData#==`.
|
|
17
|
+
#
|
|
18
|
+
# @param [Object] other the object to compare against
|
|
19
|
+
# @return [Boolean] whether the two matches are equal
|
|
20
|
+
# @example
|
|
21
|
+
# re = RE2::Regexp.new('(\d+)')
|
|
22
|
+
# re.match("abc 123") == re.match("abc 123") #=> true
|
|
23
|
+
# re.match("abc 123") == re.match("123 abc") #=> false
|
|
24
|
+
def ==(other)
|
|
25
|
+
other.is_a?(RE2::MatchData) &&
|
|
26
|
+
regexp == other.regexp &&
|
|
27
|
+
string == other.string &&
|
|
28
|
+
byteoffsets == other.byteoffsets
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
alias_method :eql?, :==
|
|
32
|
+
|
|
33
|
+
# Return a hash based on the regular expression, text, and submatch offsets
|
|
34
|
+
# so that equal {RE2::MatchData}s can be used as Hash keys.
|
|
35
|
+
#
|
|
36
|
+
# @return [Integer] the hash of the match
|
|
37
|
+
def hash
|
|
38
|
+
[regexp, string, byteoffsets].hash
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
def byteoffsets
|
|
44
|
+
Array.new(size) { |i| byteoffset(i) }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|