fast_regexp 0.6.1-aarch64-linux → 0.7.0-aarch64-linux
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/README.md +13 -1
- data/lib/fast_regexp/3.3/fast_regexp.so +0 -0
- data/lib/fast_regexp/3.4/fast_regexp.so +0 -0
- data/lib/fast_regexp/4.0/fast_regexp.so +0 -0
- data/lib/fast_regexp/version.rb +1 -1
- data/lib/fast_regexp.rb +106 -91
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb638b676baa678009503cd7a42a5a03befb73c00b79a72938480c74cd5a0538
|
|
4
|
+
data.tar.gz: 0033a9a765b873df3b9984fa138ebc534e9b3f0458d1a8018f71d3f5f768786e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d30c68136e478c8adcf061bd06d2df042e1af051bce51fc38076f960c8bb9d6eec0336c895dec3dec369401f1bb9207975a128298cd1ac03e2752f0a3281cb8
|
|
7
|
+
data.tar.gz: aad4a7176414ee857ed229bc0c4cafcfeba8e2ed93b6743c05e924c5d3c8559541cbc1849553831d475c35946ec5d4dc6f13b389f6b8f308fe88111e64e81d71
|
data/README.md
CHANGED
|
@@ -244,7 +244,18 @@ In-depth docs live under [`docs/`](docs/README.md), organized via the
|
|
|
244
244
|
- **Tutorial:** [Getting started](docs/tutorials/getting-started.md)
|
|
245
245
|
- **How-to:** [Migrate from stdlib `::Regexp`](docs/how-to/migrate-from-stdlib-regexp.md), [Handle unsupported syntax](docs/how-to/handle-unsupported-syntax.md)
|
|
246
246
|
- **Reference:** [`Fast::Regexp`](docs/reference/fast-regexp.md), [`MatchData`](docs/reference/fast-regexp-matchdata.md), [`Set`](docs/reference/fast-regexp-set.md)
|
|
247
|
-
- **Explainers:** [Engine fallback](docs/explainers/engine-fallback.md)
|
|
247
|
+
- **Explainers:** [Engine fallback](docs/explainers/engine-fallback.md), [Benchmarks](docs/explainers/benchmarks.md)
|
|
248
|
+
|
|
249
|
+
## Performance
|
|
250
|
+
|
|
251
|
+
Matching runs 8–10x stdlib `::Regexp` on a hit or a miss, scanning 2–5x,
|
|
252
|
+
`gsub` with a template 7–13x; reading a `MatchData` costs the same objects
|
|
253
|
+
as `::MatchData`, and `match?`, `=~`, `===` and `Set#match?` allocate
|
|
254
|
+
nothing. Compiling a pattern is the one place rust/regex is slower (it
|
|
255
|
+
builds its automaton up front), so compile once and keep the object. Every
|
|
256
|
+
operation, with the stdlib equivalent beside it, is on the
|
|
257
|
+
[benchmarks page](docs/explainers/benchmarks.md); reproduce with
|
|
258
|
+
`bundle exec ruby benchmark/operations.rb`.
|
|
248
259
|
|
|
249
260
|
## Development
|
|
250
261
|
|
|
@@ -253,6 +264,7 @@ bin/setup # install deps
|
|
|
253
264
|
bin/console # interactive prompt to play around
|
|
254
265
|
rake compile # (re)compile extension
|
|
255
266
|
rake spec # run tests
|
|
267
|
+
bundle exec ruby benchmark/operations.rb # the benchmarks page's table
|
|
256
268
|
```
|
|
257
269
|
|
|
258
270
|
## Contributing
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/fast_regexp/version.rb
CHANGED
data/lib/fast_regexp.rb
CHANGED
|
@@ -21,6 +21,62 @@ module Fast
|
|
|
21
21
|
candidates = [File.join(base, abi, "fast_regexp"), File.join(base, "fast_regexp")]
|
|
22
22
|
candidates.find { |stem| NATIVE_EXTENSIONS.any? { |ext| File.exist?(stem + ext) } }
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
# One match, whichever engine produced it. On the fast path the match
|
|
26
|
+
# *is* a `Fast::Regexp::Native::MatchData`, a subclass of this one
|
|
27
|
+
# defined by the extension (so it must exist before the extension
|
|
28
|
+
# loads); on the stdlib path it's an instance of this class wrapping the
|
|
29
|
+
# `::MatchData`. Same public surface either way.
|
|
30
|
+
class MatchData
|
|
31
|
+
include Enumerable
|
|
32
|
+
|
|
33
|
+
attr_reader :backend
|
|
34
|
+
|
|
35
|
+
def initialize(backend)
|
|
36
|
+
@backend = backend
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def native? = false
|
|
40
|
+
def stdlib? = true
|
|
41
|
+
|
|
42
|
+
def native = nil
|
|
43
|
+
def stdlib = @backend
|
|
44
|
+
|
|
45
|
+
def [](key) = @backend[key]
|
|
46
|
+
def to_a = @backend.to_a
|
|
47
|
+
def captures = @backend.captures
|
|
48
|
+
def named_captures = @backend.named_captures
|
|
49
|
+
def names = @backend.names
|
|
50
|
+
def size = @backend.size
|
|
51
|
+
alias_method :length, :size
|
|
52
|
+
def pre_match = @backend.pre_match
|
|
53
|
+
def post_match = @backend.post_match
|
|
54
|
+
def to_s = @backend.to_s
|
|
55
|
+
alias_method :match, :to_s
|
|
56
|
+
|
|
57
|
+
# The haystack the match was taken over: `::MatchData#string` is already
|
|
58
|
+
# a frozen snapshot, the same thing the native subclass keeps.
|
|
59
|
+
def string = @backend.string
|
|
60
|
+
|
|
61
|
+
# Byte-based offsets. `::MatchData#byteoffset` exists since Ruby 3.2;
|
|
62
|
+
# its `byte_begin`/`byte_end` don't (3.4 added `bytebegin`/`byteend`),
|
|
63
|
+
# so those two derive from `byteoffset` here.
|
|
64
|
+
def byteoffset(key) = @backend.byteoffset(key)
|
|
65
|
+
def byte_begin(key) = byteoffset(key)[0]
|
|
66
|
+
def byte_end(key) = byteoffset(key)[1]
|
|
67
|
+
|
|
68
|
+
def each(&block) = to_a.each(&block)
|
|
69
|
+
def values_at(*indices) = indices.map { |i| self[i] }
|
|
70
|
+
|
|
71
|
+
def ==(other)
|
|
72
|
+
other.is_a?(MatchData) && to_a == other.to_a && string == other.string
|
|
73
|
+
end
|
|
74
|
+
alias_method :eql?, :==
|
|
75
|
+
|
|
76
|
+
def hash = [to_a, string].hash
|
|
77
|
+
|
|
78
|
+
def inspect = "#<Fast::Regexp::MatchData #{to_s.inspect}>"
|
|
79
|
+
end
|
|
24
80
|
end
|
|
25
81
|
end
|
|
26
82
|
|
|
@@ -30,6 +86,22 @@ require native
|
|
|
30
86
|
|
|
31
87
|
module Fast
|
|
32
88
|
class Regexp
|
|
89
|
+
class Native
|
|
90
|
+
class MatchData
|
|
91
|
+
def native? = true
|
|
92
|
+
def stdlib? = false
|
|
93
|
+
|
|
94
|
+
def native = self
|
|
95
|
+
def stdlib = nil
|
|
96
|
+
def backend = self
|
|
97
|
+
|
|
98
|
+
# Immutable, so a copy is the object itself (the wrapped class has no
|
|
99
|
+
# allocator for `dup`/`clone` to go through).
|
|
100
|
+
def dup = self
|
|
101
|
+
def clone(freeze: nil) = self
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
33
105
|
RUBY_FLAG_MAP = {
|
|
34
106
|
::Regexp::IGNORECASE => "i",
|
|
35
107
|
::Regexp::EXTENDED => "x",
|
|
@@ -45,13 +117,21 @@ module Fast
|
|
|
45
117
|
allocate.tap { |re| re.send(:initialize, translated, original: pattern, **opts) }
|
|
46
118
|
end
|
|
47
119
|
|
|
48
|
-
# Bulk-compile a
|
|
49
|
-
#
|
|
120
|
+
# Bulk-compile a set of patterns. Two call shapes:
|
|
121
|
+
#
|
|
122
|
+
# Fast::Regexp.create_many(word: '\w+', num: '\d+')
|
|
123
|
+
# # => { word: #<Fast::Regexp ...>, num: #<Fast::Regexp ...> }
|
|
50
124
|
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
125
|
+
# Fast::Regexp.create_many('\w+', '\d+')
|
|
126
|
+
# # => [#<Fast::Regexp ...>, #<Fast::Regexp ...>]
|
|
127
|
+
#
|
|
128
|
+
# Mixing the two raises ArgumentError — pick one shape per call.
|
|
129
|
+
def create_many(*patterns, **named)
|
|
130
|
+
if !patterns.empty? && !named.empty?
|
|
131
|
+
raise ArgumentError, "create_many accepts positional patterns OR keyword patterns, not both"
|
|
132
|
+
end
|
|
133
|
+
return named.transform_values { |pat| new(pat) } if patterns.empty?
|
|
134
|
+
patterns.map { |pat| new(pat) }
|
|
55
135
|
end
|
|
56
136
|
|
|
57
137
|
private
|
|
@@ -88,8 +168,9 @@ module Fast
|
|
|
88
168
|
|
|
89
169
|
def match(haystack)
|
|
90
170
|
haystack = coerce_string(haystack)
|
|
91
|
-
|
|
92
|
-
|
|
171
|
+
return @backend._native_match(haystack) if fast?
|
|
172
|
+
m = @backend.match(haystack)
|
|
173
|
+
m && MatchData.new(m)
|
|
93
174
|
end
|
|
94
175
|
|
|
95
176
|
def match?(haystack)
|
|
@@ -105,8 +186,10 @@ module Fast
|
|
|
105
186
|
# also returns bytes here for API consistency).
|
|
106
187
|
def =~(other)
|
|
107
188
|
return nil unless other.respond_to?(:to_str)
|
|
108
|
-
|
|
109
|
-
|
|
189
|
+
haystack = other.to_str
|
|
190
|
+
return @backend._native_find(haystack) if fast?
|
|
191
|
+
m = @backend.match(haystack)
|
|
192
|
+
m && m.byteoffset(0)[0]
|
|
110
193
|
end
|
|
111
194
|
|
|
112
195
|
def scan(haystack)
|
|
@@ -115,22 +198,17 @@ module Fast
|
|
|
115
198
|
|
|
116
199
|
def scan_matches(haystack)
|
|
117
200
|
haystack = coerce_string(haystack)
|
|
118
|
-
if fast?
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
haystack.scan(@backend) { results << MatchData.new(::Regexp.last_match, haystack) }
|
|
123
|
-
results
|
|
124
|
-
end
|
|
201
|
+
return @backend.scan_matches(haystack) if fast?
|
|
202
|
+
results = []
|
|
203
|
+
haystack.scan(@backend) { results << MatchData.new(::Regexp.last_match) }
|
|
204
|
+
results
|
|
125
205
|
end
|
|
126
206
|
|
|
127
207
|
def sub(haystack, replacement = nil, literal: false, &block)
|
|
128
208
|
haystack = coerce_string(haystack)
|
|
129
|
-
if
|
|
209
|
+
if block_given?
|
|
130
210
|
raise ArgumentError, "wrong number of arguments (given 2, expected 1 with block)" if replacement
|
|
131
|
-
|
|
132
|
-
return haystack.dup unless m
|
|
133
|
-
"#{m.pre_match}#{block.call(m)}#{m.post_match}"
|
|
211
|
+
fast? ? @backend._native_sub_block(haystack, &block) : stdlib_sub_with_block(haystack, &block)
|
|
134
212
|
else
|
|
135
213
|
raise ArgumentError, "wrong number of arguments (given 1, expected 2)" if replacement.nil?
|
|
136
214
|
replacement = coerce_string(replacement)
|
|
@@ -144,9 +222,9 @@ module Fast
|
|
|
144
222
|
|
|
145
223
|
def gsub(haystack, replacement = nil, literal: false, &block)
|
|
146
224
|
haystack = coerce_string(haystack)
|
|
147
|
-
if
|
|
225
|
+
if block_given?
|
|
148
226
|
raise ArgumentError, "wrong number of arguments (given 2, expected 1 with block)" if replacement
|
|
149
|
-
fast? ?
|
|
227
|
+
fast? ? @backend._native_gsub_block(haystack, &block) : stdlib_gsub_with_block(haystack, &block)
|
|
150
228
|
else
|
|
151
229
|
raise ArgumentError, "wrong number of arguments (given 1, expected 2)" if replacement.nil?
|
|
152
230
|
replacement = coerce_string(replacement)
|
|
@@ -246,29 +324,14 @@ module Fast
|
|
|
246
324
|
end
|
|
247
325
|
end
|
|
248
326
|
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
matches = scan_matches(haystack)
|
|
254
|
-
return haystack.dup if matches.empty?
|
|
255
|
-
|
|
256
|
-
out = String.new(encoding: Encoding::UTF_8)
|
|
257
|
-
cursor = 0
|
|
258
|
-
matches.each do |m|
|
|
259
|
-
bs, be = m.byteoffset(0)
|
|
260
|
-
out << haystack.byteslice(cursor, bs - cursor) if bs > cursor
|
|
261
|
-
out << yield(m).to_s
|
|
262
|
-
cursor = be
|
|
263
|
-
end
|
|
264
|
-
out << haystack.byteslice(cursor, haystack.bytesize - cursor) if cursor < haystack.bytesize
|
|
265
|
-
out
|
|
327
|
+
# Stdlib path: String#sub/#gsub already do single-pass iterate-and-replace
|
|
328
|
+
# and set $~ inside the block, so wrap the current ::MatchData and yield.
|
|
329
|
+
def stdlib_sub_with_block(haystack)
|
|
330
|
+
haystack.sub(@backend) { yield(MatchData.new(::Regexp.last_match)).to_s }
|
|
266
331
|
end
|
|
267
332
|
|
|
268
|
-
# Stdlib path: String#gsub already does single-pass iterate-and-replace
|
|
269
|
-
# and sets $~ inside the block, so wrap the current ::MatchData and yield.
|
|
270
333
|
def stdlib_gsub_with_block(haystack)
|
|
271
|
-
haystack.gsub(@backend) { yield(MatchData.new(::Regexp.last_match
|
|
334
|
+
haystack.gsub(@backend) { yield(MatchData.new(::Regexp.last_match)).to_s }
|
|
272
335
|
end
|
|
273
336
|
|
|
274
337
|
def coerce_string(value)
|
|
@@ -276,53 +339,5 @@ module Fast
|
|
|
276
339
|
return value.to_str if value.respond_to?(:to_str)
|
|
277
340
|
raise TypeError, "no implicit conversion of #{value.class} into String"
|
|
278
341
|
end
|
|
279
|
-
|
|
280
|
-
# Wraps either a Fast::Regexp::Native::MatchData or a stdlib ::MatchData
|
|
281
|
-
# so callers see one type regardless of which backend ran.
|
|
282
|
-
class MatchData
|
|
283
|
-
include Enumerable
|
|
284
|
-
|
|
285
|
-
attr_reader :backend, :string
|
|
286
|
-
|
|
287
|
-
def initialize(backend, haystack)
|
|
288
|
-
@backend = backend
|
|
289
|
-
@string = haystack
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
def native? = @backend.is_a?(Fast::Regexp::Native::MatchData)
|
|
293
|
-
def stdlib? = !native?
|
|
294
|
-
|
|
295
|
-
def native = native? ? @backend : nil
|
|
296
|
-
def stdlib = stdlib? ? @backend : nil
|
|
297
|
-
|
|
298
|
-
def [](key) = @backend[key]
|
|
299
|
-
def to_a = @backend.to_a
|
|
300
|
-
def captures = @backend.captures
|
|
301
|
-
def named_captures = @backend.named_captures
|
|
302
|
-
def names = @backend.names
|
|
303
|
-
def size = @backend.size
|
|
304
|
-
alias_method :length, :size
|
|
305
|
-
def pre_match = @backend.pre_match
|
|
306
|
-
def post_match = @backend.post_match
|
|
307
|
-
def to_s = @backend.to_s
|
|
308
|
-
|
|
309
|
-
# Byte-based offsets. Both backends expose these (stdlib MatchData has
|
|
310
|
-
# `byteoffset` / `byte_begin` / `byte_end` since Ruby 3.2).
|
|
311
|
-
def byteoffset(key) = @backend.byteoffset(key)
|
|
312
|
-
def byte_begin(key) = @backend.byte_begin(key)
|
|
313
|
-
def byte_end(key) = @backend.byte_end(key)
|
|
314
|
-
|
|
315
|
-
def each(&block) = to_a.each(&block)
|
|
316
|
-
def values_at(*indices) = indices.map { |i| self[i] }
|
|
317
|
-
|
|
318
|
-
def ==(other)
|
|
319
|
-
other.is_a?(MatchData) && to_a == other.to_a && string == other.string
|
|
320
|
-
end
|
|
321
|
-
alias_method :eql?, :==
|
|
322
|
-
|
|
323
|
-
def hash = [to_a, string].hash
|
|
324
|
-
|
|
325
|
-
def inspect = "#<Fast::Regexp::MatchData #{to_s.inspect}>"
|
|
326
|
-
end
|
|
327
342
|
end
|
|
328
343
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fast_regexp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Eric Jacobs
|
|
@@ -9,10 +9,10 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: exe
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-
|
|
12
|
+
date: 2026-09-19 00:00:00.000000000 Z
|
|
13
13
|
dependencies: []
|
|
14
14
|
description: 'Ruby bindings to rust/regex with a Fast::Regexp API: MatchData, sub/gsub,
|
|
15
|
-
===, =~, named captures
|
|
15
|
+
===, =~, & named captures.'
|
|
16
16
|
email:
|
|
17
17
|
- eric@ebj.dev
|
|
18
18
|
executables: []
|
|
@@ -31,6 +31,7 @@ licenses:
|
|
|
31
31
|
- MIT
|
|
32
32
|
metadata:
|
|
33
33
|
bug_tracker_uri: https://github.com/jetpks/fast_regexp/issues
|
|
34
|
+
changelog_uri: https://github.com/jetpks/fast_regexp/blob/main/CHANGELOG.md
|
|
34
35
|
homepage_uri: https://github.com/jetpks/fast_regexp
|
|
35
36
|
source_code_uri: https://github.com/jetpks/fast_regexp
|
|
36
37
|
post_install_message:
|