corvus_regex_ecma262 0.1.1
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 +7 -0
- data/Cargo.lock +293 -0
- data/Cargo.toml +8 -0
- data/LICENSE +201 -0
- data/README.md +100 -0
- data/ext/corvus_regex_ecma262/Cargo.toml +16 -0
- data/ext/corvus_regex_ecma262/clippy.toml +2 -0
- data/ext/corvus_regex_ecma262/extconf.rb +6 -0
- data/ext/corvus_regex_ecma262/rustfmt.toml +2 -0
- data/ext/corvus_regex_ecma262/src/lib.rs +400 -0
- data/lib/corvus_regex_ecma262/version.rb +5 -0
- data/lib/corvus_regex_ecma262.rb +297 -0
- metadata +74 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
//! The native extension of the corvus_regex_ecma262 gem: the corvus-regex-ecma262 crate over Ruby strings. The Ruby
|
|
2
|
+
//! library (lib/corvus_regex_ecma262.rb) is the API; this is the part that matches.
|
|
3
|
+
//!
|
|
4
|
+
//! A string is matched as it is held: a UTF-8 (or US-ASCII, or ASCII-only binary) string as UTF-8, positions in bytes,
|
|
5
|
+
//! and a UTF-16LE string as UTF-16, positions in code units. A match's groups come back as one flat Array of integers:
|
|
6
|
+
//! the start and the end of each group (the whole match first), then of the group of each name, -1 for a group that
|
|
7
|
+
//! did not take part. The Ruby library turns them into a Match.
|
|
8
|
+
|
|
9
|
+
use std::time::Duration;
|
|
10
|
+
|
|
11
|
+
use corvus_regex_ecma262 as ecma;
|
|
12
|
+
use magnus::encoding::Coderange;
|
|
13
|
+
use magnus::encoding::EncodingCapable;
|
|
14
|
+
use magnus::prelude::*;
|
|
15
|
+
use magnus::rb_sys::AsRawValue;
|
|
16
|
+
use magnus::{Error, ExceptionClass, RArray, RModule, RString, Ruby, Value, function, method};
|
|
17
|
+
|
|
18
|
+
fn error_class(ruby: &Ruby, name: &str) -> ExceptionClass {
|
|
19
|
+
let module: RModule = ruby.class_object().const_get("CorvusRegexEcma262").expect("the module is defined");
|
|
20
|
+
module.const_get(name).expect("the error classes are defined")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fn compile_error(ruby: &Ruby, e: ecma::Error) -> Error {
|
|
24
|
+
let class = match e {
|
|
25
|
+
ecma::Error::TooLarge(_) => "PatternTooLargeError",
|
|
26
|
+
ecma::Error::Flags(_) => "InvalidFlagsError",
|
|
27
|
+
_ => "InvalidPatternError",
|
|
28
|
+
};
|
|
29
|
+
Error::new(error_class(ruby, class), e.to_string())
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn limit_error(ruby: &Ruby, e: ecma::MatchError) -> Error {
|
|
33
|
+
Error::new(error_class(ruby, "MatchLimitError"), e.to_string())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// The text of a call: UTF-8 borrowed from the string, or its UTF-16LE read in place where its bytes are aligned for
|
|
37
|
+
/// u16 (as a string's own buffer is) and copied into units where they are not.
|
|
38
|
+
enum Text<'a> {
|
|
39
|
+
Utf8(&'a str),
|
|
40
|
+
Utf16(std::borrow::Cow<'a, [u16]>),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[derive(Clone, Copy)]
|
|
44
|
+
enum View<'a> {
|
|
45
|
+
Utf8(&'a str),
|
|
46
|
+
Utf16(&'a [u16]),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
impl Text<'_> {
|
|
50
|
+
fn view(&self) -> View<'_> {
|
|
51
|
+
match self {
|
|
52
|
+
Text::Utf8(s) => View::Utf8(s),
|
|
53
|
+
Text::Utf16(u) => View::Utf16(u),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// The text of a string, as its encoding says it is held. SAFETY: the caller must not let Ruby code run, nor the
|
|
59
|
+
/// string change, while the text is borrowed (a call that yields locks the string first).
|
|
60
|
+
unsafe fn text_of<'a>(ruby: &Ruby, s: RString) -> Result<Text<'a>, Error> {
|
|
61
|
+
let enc = s.enc_get();
|
|
62
|
+
// SAFETY: the bytes stay as they are for the call, as the caller ensures.
|
|
63
|
+
let bytes: &'a [u8] = unsafe { std::mem::transmute::<&[u8], &'a [u8]>(s.as_slice()) };
|
|
64
|
+
if enc == ruby.find_encindex("UTF-16LE")? {
|
|
65
|
+
// SAFETY: any two bytes are a u16; align_to gives the aligned middle, which is all of it when the start is
|
|
66
|
+
// aligned and the length even. The targets the gem is built for are little-endian, as UTF-16LE is.
|
|
67
|
+
let (head, units, tail) = unsafe { bytes.align_to::<u16>() };
|
|
68
|
+
if cfg!(target_endian = "little") && head.is_empty() && tail.is_empty() {
|
|
69
|
+
return Ok(Text::Utf16(std::borrow::Cow::Borrowed(units)));
|
|
70
|
+
}
|
|
71
|
+
return Ok(Text::Utf16(std::borrow::Cow::Owned(
|
|
72
|
+
bytes.as_chunks::<2>().0.iter().map(|c| u16::from_le_bytes(*c)).collect(),
|
|
73
|
+
)));
|
|
74
|
+
}
|
|
75
|
+
let utf8 = enc == ruby.utf8_encindex() || enc == ruby.usascii_encindex();
|
|
76
|
+
// Ruby keeps what it has read of a string's bytes (its coderange), so this reads them once for the string's life.
|
|
77
|
+
match s.enc_coderange_scan() {
|
|
78
|
+
Coderange::SevenBit => {}
|
|
79
|
+
Coderange::Valid if utf8 => {}
|
|
80
|
+
_ if !utf8 && enc != ruby.ascii8bit_encindex() => {
|
|
81
|
+
return Err(Error::new(
|
|
82
|
+
ruby.exception_encoding_error(),
|
|
83
|
+
"the string must be UTF-8 (or US-ASCII, or binary holding UTF-8) or UTF-16LE",
|
|
84
|
+
));
|
|
85
|
+
}
|
|
86
|
+
_ => {
|
|
87
|
+
if std::str::from_utf8(bytes).is_err() {
|
|
88
|
+
return Err(Error::new(ruby.exception_arg_error(), "invalid byte sequence in UTF-8"));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// SAFETY: checked above (seven-bit, valid UTF-8, or read as UTF-8 just now).
|
|
93
|
+
Ok(Text::Utf8(unsafe { std::str::from_utf8_unchecked(bytes) }))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn limit_of(timeout: Option<f64>) -> Option<Duration> {
|
|
97
|
+
timeout.map(|t| Duration::from_secs_f64(t.max(0.0)))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#[magnus::wrap(class = "CorvusRegexEcma262::Native::Regex", free_immediately, size)]
|
|
101
|
+
struct NativeRegex {
|
|
102
|
+
inner: ecma::Regex,
|
|
103
|
+
/// Neither u nor v: a match is by UTF-16 code unit.
|
|
104
|
+
legacy: bool,
|
|
105
|
+
global: bool,
|
|
106
|
+
names: Vec<String>,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
impl NativeRegex {
|
|
110
|
+
/// The groups of a match as the flat Array the Ruby library reads.
|
|
111
|
+
fn flat<H: ?Sized + ecma::Haystack>(&self, ruby: &Ruby, caps: &ecma::Captures<'_, H>) -> RArray {
|
|
112
|
+
let out = ruby.ary_new_capa(2 * (caps.len() + self.names.len()));
|
|
113
|
+
let push = |m: Option<(usize, usize)>| {
|
|
114
|
+
let (s, e) = m.map_or((-1, -1), |(s, e)| (s as i64, e as i64));
|
|
115
|
+
let _ = out.push(s);
|
|
116
|
+
let _ = out.push(e);
|
|
117
|
+
};
|
|
118
|
+
for m in caps.iter() {
|
|
119
|
+
push(m.map(|m| (m.start(), m.end())));
|
|
120
|
+
}
|
|
121
|
+
for name in &self.names {
|
|
122
|
+
push(caps.name(name).map(|m| (m.start(), m.end())));
|
|
123
|
+
}
|
|
124
|
+
out
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn captures_at(
|
|
128
|
+
&self,
|
|
129
|
+
ruby: &Ruby,
|
|
130
|
+
view: View<'_>,
|
|
131
|
+
start: usize,
|
|
132
|
+
limit: Option<Duration>,
|
|
133
|
+
) -> Result<Option<RArray>, Error> {
|
|
134
|
+
Ok(match view {
|
|
135
|
+
View::Utf8(s) => {
|
|
136
|
+
let caps = match limit {
|
|
137
|
+
Some(l) => self.inner.try_captures_at(s, start, l).map_err(|e| limit_error(ruby, e))?,
|
|
138
|
+
None => self.inner.captures_at(s, start),
|
|
139
|
+
};
|
|
140
|
+
caps.map(|c| self.flat(ruby, &c))
|
|
141
|
+
}
|
|
142
|
+
View::Utf16(u) => {
|
|
143
|
+
let caps = match limit {
|
|
144
|
+
Some(l) => self.inner.try_captures_at_utf16(u, start, l).map_err(|e| limit_error(ruby, e))?,
|
|
145
|
+
None => self.inner.captures_at_utf16(u, start),
|
|
146
|
+
};
|
|
147
|
+
caps.map(|c| self.flat(ruby, &c))
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// The position after an empty match at `at`: one code point on with u or v, one code unit without (over UTF-8,
|
|
153
|
+
/// from a character beyond the Basic Multilingual Plane that is its third byte, between its halves).
|
|
154
|
+
fn advance(&self, view: View<'_>, at: usize) -> usize {
|
|
155
|
+
match view {
|
|
156
|
+
View::Utf8(s) => {
|
|
157
|
+
let b = s.as_bytes();
|
|
158
|
+
if self.legacy && at < b.len() && b[at] >= 0xF0 {
|
|
159
|
+
return at + 2;
|
|
160
|
+
}
|
|
161
|
+
let mut next = at + 1;
|
|
162
|
+
while next < b.len() && (b[next] & 0xC0) == 0x80 {
|
|
163
|
+
next += 1;
|
|
164
|
+
}
|
|
165
|
+
next
|
|
166
|
+
}
|
|
167
|
+
View::Utf16(u) => {
|
|
168
|
+
if !self.legacy
|
|
169
|
+
&& at + 1 < u.len()
|
|
170
|
+
&& (0xD800..0xDC00).contains(&u[at])
|
|
171
|
+
&& (0xDC00..0xE000).contains(&u[at + 1])
|
|
172
|
+
{
|
|
173
|
+
at + 2
|
|
174
|
+
} else {
|
|
175
|
+
at + 1
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fn len(view: View<'_>) -> usize {
|
|
182
|
+
match view {
|
|
183
|
+
View::Utf8(s) => s.len(),
|
|
184
|
+
View::Utf16(u) => u.len(),
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Locks a string against change while Ruby code runs inside a call that has its text borrowed. A frozen string
|
|
190
|
+
/// cannot change, and is not locked (Ruby 4.0 refuses to lock one).
|
|
191
|
+
struct Locked(Option<Value>);
|
|
192
|
+
|
|
193
|
+
impl Locked {
|
|
194
|
+
fn new(s: RString) -> Locked {
|
|
195
|
+
if s.is_frozen() {
|
|
196
|
+
return Locked(None);
|
|
197
|
+
}
|
|
198
|
+
// SAFETY: rb_str_locktmp raises if the string is already locked, which a nested call does not do here.
|
|
199
|
+
unsafe { rb_sys::rb_str_locktmp(s.as_raw()) };
|
|
200
|
+
Locked(Some(s.as_value()))
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
impl Drop for Locked {
|
|
205
|
+
fn drop(&mut self) {
|
|
206
|
+
if let Some(s) = self.0 {
|
|
207
|
+
// SAFETY: the string was locked by new.
|
|
208
|
+
unsafe { rb_sys::rb_str_unlocktmp(s.as_raw()) };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
impl NativeRegex {
|
|
214
|
+
fn new(ruby: &Ruby, pattern: RString, flags: RString) -> Result<NativeRegex, Error> {
|
|
215
|
+
let pattern = pattern.to_string()?;
|
|
216
|
+
let flags = flags.to_string()?;
|
|
217
|
+
let inner = ecma::Regex::new(&pattern, &flags).map_err(|e| compile_error(ruby, e))?;
|
|
218
|
+
let written = format!("{}", inner.flags());
|
|
219
|
+
let names = inner.group_names().map(str::to_owned).collect();
|
|
220
|
+
Ok(NativeRegex {
|
|
221
|
+
legacy: !written.contains('u') && !written.contains('v'),
|
|
222
|
+
global: written.contains('g'),
|
|
223
|
+
inner,
|
|
224
|
+
names,
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fn is_valid(pattern: RString, flags: RString) -> Result<bool, Error> {
|
|
229
|
+
Ok(ecma::Regex::is_valid(&pattern.to_string()?, &flags.to_string()?))
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fn source(&self) -> String {
|
|
233
|
+
self.inner.as_str().to_owned()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fn flags(&self) -> String {
|
|
237
|
+
format!("{}", self.inner.flags())
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
fn group_count(&self) -> usize {
|
|
241
|
+
self.inner.captures_len() - 1
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
fn names(&self) -> Vec<String> {
|
|
245
|
+
self.names.clone()
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
fn global(&self) -> bool {
|
|
249
|
+
self.global
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// Whether the pattern matches at or after start (an engine position).
|
|
253
|
+
fn test(ruby: &Ruby, rb_self: &Self, s: RString, start: usize, timeout: Option<f64>) -> Result<bool, Error> {
|
|
254
|
+
// SAFETY: no Ruby code runs while the text is borrowed.
|
|
255
|
+
let text = unsafe { text_of(ruby, s)? };
|
|
256
|
+
let view = text.view();
|
|
257
|
+
if start > NativeRegex::len(view) {
|
|
258
|
+
return Ok(false);
|
|
259
|
+
}
|
|
260
|
+
let inner = &rb_self.inner;
|
|
261
|
+
match (view, limit_of(timeout)) {
|
|
262
|
+
(View::Utf8(t), None) => Ok(inner.is_match_at(t, start)),
|
|
263
|
+
(View::Utf16(u), None) => Ok(inner.is_match_at_utf16(u, start)),
|
|
264
|
+
(View::Utf8(t), Some(l)) => {
|
|
265
|
+
inner.try_find_at(t, start, l).map(|m| m.is_some()).map_err(|e| limit_error(ruby, e))
|
|
266
|
+
}
|
|
267
|
+
(View::Utf16(u), Some(l)) => {
|
|
268
|
+
inner.try_find_at_utf16(u, start, l).map(|m| m.is_some()).map_err(|e| limit_error(ruby, e))
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// The first match at or after start (an engine position): its flat groups, or nil.
|
|
274
|
+
fn exec(
|
|
275
|
+
ruby: &Ruby,
|
|
276
|
+
rb_self: &Self,
|
|
277
|
+
s: RString,
|
|
278
|
+
start: usize,
|
|
279
|
+
timeout: Option<f64>,
|
|
280
|
+
) -> Result<Option<RArray>, Error> {
|
|
281
|
+
// SAFETY: no Ruby code runs while the text is borrowed.
|
|
282
|
+
let text = unsafe { text_of(ruby, s)? };
|
|
283
|
+
let view = text.view();
|
|
284
|
+
if start > NativeRegex::len(view) {
|
|
285
|
+
return Ok(None);
|
|
286
|
+
}
|
|
287
|
+
rb_self.captures_at(ruby, view, start, limit_of(timeout))
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// Every match of a global search, as matchAll makes one, each yielded as its flat groups. Returns the number of
|
|
291
|
+
/// matches.
|
|
292
|
+
fn each(ruby: &Ruby, rb_self: &Self, s: RString) -> Result<usize, Error> {
|
|
293
|
+
let _lock = Locked::new(s);
|
|
294
|
+
// SAFETY: the string is locked against change while the block runs.
|
|
295
|
+
let text = unsafe { text_of(ruby, s)? };
|
|
296
|
+
let view = text.view();
|
|
297
|
+
let len = NativeRegex::len(view);
|
|
298
|
+
let mut at = 0;
|
|
299
|
+
let mut n = 0;
|
|
300
|
+
while at <= len {
|
|
301
|
+
let Some(groups) = rb_self.captures_at(ruby, view, at, None)? else { break };
|
|
302
|
+
let (s, e): (i64, i64) = (groups.entry(0)?, groups.entry(1)?);
|
|
303
|
+
let _: Value = ruby.yield_value(groups)?;
|
|
304
|
+
n += 1;
|
|
305
|
+
at = if e == s { rb_self.advance(view, e as usize) } else { e as usize };
|
|
306
|
+
}
|
|
307
|
+
Ok(n)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// The number of matches of a global search, without yielding them.
|
|
311
|
+
fn count(ruby: &Ruby, rb_self: &Self, s: RString) -> Result<usize, Error> {
|
|
312
|
+
// SAFETY: no Ruby code runs while the text is borrowed.
|
|
313
|
+
let text = unsafe { text_of(ruby, s)? };
|
|
314
|
+
Ok(match text.view() {
|
|
315
|
+
View::Utf8(t) => rb_self.inner.find_iter(t).count(),
|
|
316
|
+
View::Utf16(u) => rb_self.inner.find_iter_utf16(u).count(),
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/// The text with the first match replaced, or every match with all, by a template: the bytes, in the text's
|
|
321
|
+
/// encoding.
|
|
322
|
+
fn replace(ruby: &Ruby, rb_self: &Self, s: RString, template: RString, all: bool) -> Result<RString, Error> {
|
|
323
|
+
// SAFETY: no Ruby code runs while the texts are borrowed.
|
|
324
|
+
let text = unsafe { text_of(ruby, s)? };
|
|
325
|
+
let template = unsafe { text_of(ruby, template)? };
|
|
326
|
+
let inner = &rb_self.inner;
|
|
327
|
+
Ok(match (text.view(), template.view()) {
|
|
328
|
+
(View::Utf8(t), View::Utf8(r)) => {
|
|
329
|
+
let out = if all { inner.replace_all(t, r) } else { inner.replace(t, r) };
|
|
330
|
+
ruby.str_from_slice(out.as_bytes())
|
|
331
|
+
}
|
|
332
|
+
(View::Utf16(t), View::Utf16(r)) => {
|
|
333
|
+
let out = if all { inner.replace_all_utf16(t, r) } else { inner.replace_utf16(t, r) };
|
|
334
|
+
let bytes: Vec<u8> = out.iter().flat_map(|u| u.to_le_bytes()).collect();
|
|
335
|
+
ruby.str_from_slice(&bytes)
|
|
336
|
+
}
|
|
337
|
+
_ => return Err(Error::new(ruby.exception_arg_error(), "the replacement's encoding is not the string's")),
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/// The text split around the matches: the pieces' bytes in the text's encoding, nil for a group that did not take
|
|
342
|
+
/// part.
|
|
343
|
+
fn split(ruby: &Ruby, rb_self: &Self, s: RString, limit: Option<usize>) -> Result<RArray, Error> {
|
|
344
|
+
// SAFETY: no Ruby code runs while the text is borrowed.
|
|
345
|
+
let text = unsafe { text_of(ruby, s)? };
|
|
346
|
+
let inner = &rb_self.inner;
|
|
347
|
+
let out = ruby.ary_new();
|
|
348
|
+
match text.view() {
|
|
349
|
+
View::Utf8(t) => {
|
|
350
|
+
let pieces = match limit {
|
|
351
|
+
Some(n) => inner.splitn(t, n),
|
|
352
|
+
None => inner.split(t),
|
|
353
|
+
};
|
|
354
|
+
for p in pieces {
|
|
355
|
+
out.push(p.map(|p| ruby.str_from_slice(p.as_bytes())))?;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
View::Utf16(u) => {
|
|
359
|
+
let pieces = match limit {
|
|
360
|
+
Some(n) => inner.splitn_utf16(u, n),
|
|
361
|
+
None => inner.split_utf16(u),
|
|
362
|
+
};
|
|
363
|
+
for p in pieces {
|
|
364
|
+
out.push(p.map(|p| {
|
|
365
|
+
let bytes: Vec<u8> = p.iter().flat_map(|u| u.to_le_bytes()).collect();
|
|
366
|
+
ruby.str_from_slice(&bytes)
|
|
367
|
+
}))?;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
Ok(out)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
#[magnus::init]
|
|
376
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
377
|
+
let module = ruby.define_module("CorvusRegexEcma262")?;
|
|
378
|
+
let error = module.define_error("Error", ruby.exception_standard_error())?;
|
|
379
|
+
module.define_error("InvalidPatternError", error)?;
|
|
380
|
+
module.define_error("InvalidFlagsError", error)?;
|
|
381
|
+
module.define_error("PatternTooLargeError", error)?;
|
|
382
|
+
module.define_error("MatchLimitError", error)?;
|
|
383
|
+
let native = module.define_module("Native")?;
|
|
384
|
+
native.define_singleton_method("crate_version", function!(|| env!("CARGO_PKG_VERSION"), 0))?;
|
|
385
|
+
let regex = native.define_class("Regex", ruby.class_object())?;
|
|
386
|
+
regex.define_singleton_method("new", function!(NativeRegex::new, 2))?;
|
|
387
|
+
regex.define_singleton_method("valid?", function!(NativeRegex::is_valid, 2))?;
|
|
388
|
+
regex.define_method("source", method!(NativeRegex::source, 0))?;
|
|
389
|
+
regex.define_method("flags", method!(NativeRegex::flags, 0))?;
|
|
390
|
+
regex.define_method("group_count", method!(NativeRegex::group_count, 0))?;
|
|
391
|
+
regex.define_method("names", method!(NativeRegex::names, 0))?;
|
|
392
|
+
regex.define_method("global?", method!(NativeRegex::global, 0))?;
|
|
393
|
+
regex.define_method("test", method!(NativeRegex::test, 3))?;
|
|
394
|
+
regex.define_method("exec", method!(NativeRegex::exec, 3))?;
|
|
395
|
+
regex.define_method("each", method!(NativeRegex::each, 1))?;
|
|
396
|
+
regex.define_method("count", method!(NativeRegex::count, 1))?;
|
|
397
|
+
regex.define_method("replace", method!(NativeRegex::replace, 3))?;
|
|
398
|
+
regex.define_method("split", method!(NativeRegex::split, 2))?;
|
|
399
|
+
Ok(())
|
|
400
|
+
}
|