Class: Fast::Regexp

Inherits:
Object
  • Object
show all
Defined in:
lib/fast_regexp.rb,
lib/fast_regexp.rb,
lib/fast_regexp/version.rb

Overview

Façade over rust/regex with a transparent fallback to stdlib ‘::Regexp`.

‘Fast::Regexp.new(pattern)` first tries to compile with rust/regex (fast, GVL-releasing, byte-based). If the pattern uses features rust/regex does not support (lookaround, backreferences, possessive quantifiers, etc.) we fall back to `::Regexp` so consumers don’t have to juggle two libraries.

Defined Under Namespace

Classes: MatchData

Constant Summary collapse

RUBY_FLAG_MAP =
{
  ::Regexp::IGNORECASE => "i",
  ::Regexp::EXTENDED => "x",
  ::Regexp::MULTILINE => "s" # Ruby's /m = dotall; in rust/regex that is (?s).
}.freeze
VERSION =
"0.5.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pattern, original: pattern, **opts) ⇒ Regexp

Internal — use ‘Fast::Regexp.new`. `original` is the unmodified input (String or ::Regexp) so we can build an accurate stdlib fallback.



51
52
53
54
# File 'lib/fast_regexp.rb', line 51

def initialize(pattern, original: pattern, **opts)
  @pattern = pattern
  @backend = compile_backend(pattern, original, opts)
end

Instance Attribute Details

#backendObject (readonly)

Returns the value of attribute backend.



47
48
49
# File 'lib/fast_regexp.rb', line 47

def backend
  @backend
end

#patternObject (readonly) Also known as: to_s

Returns the value of attribute pattern.



47
48
49
# File 'lib/fast_regexp.rb', line 47

def pattern
  @pattern
end

Class Method Details

.new(pattern, **opts) ⇒ Object

Compile a pattern. Accepts a String or a ‘::Regexp` (flags are translated into a leading `(?…)` group so rust/regex sees them). Falls back to `::Regexp` if rust/regex rejects the pattern.



32
33
34
35
# File 'lib/fast_regexp.rb', line 32

def new(pattern, **opts)
  translated = pattern.is_a?(::Regexp) ? translate_regexp(pattern) : pattern
  allocate.tap { |re| re.send(:initialize, translated, original: pattern, **opts) }
end

Instance Method Details

#===(other) ⇒ Object



73
74
75
76
# File 'lib/fast_regexp.rb', line 73

def ===(other)
  return false unless other.respond_to?(:to_str)
  match?(other.to_str)
end

#=~(other) ⇒ Object

Byte offset of the first match (rust/regex is byte-based; stdlib path also returns bytes here for API consistency).



80
81
82
83
84
# File 'lib/fast_regexp.rb', line 80

def =~(other)
  return nil unless other.respond_to?(:to_str)
  m = match(other.to_str)
  m && m.byte_begin(0)
end

#captures_countObject

On the fast path this comes from rust/regex directly. On the stdlib fallback we walk the source counting capturing groups while honoring escapes, character classes, and non-capturing / lookaround prefixes.



138
139
140
141
# File 'lib/fast_regexp.rb', line 138

def captures_count
  return @backend.captures_count if fast?
  count_stdlib_captures(@backend.source)
end

#fast?Boolean

Returns:

  • (Boolean)


56
# File 'lib/fast_regexp.rb', line 56

def fast? = @backend.is_a?(Native)

#gsub(haystack, replacement = nil, literal: false, &block) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/fast_regexp.rb', line 119

def gsub(haystack, replacement = nil, literal: false, &block)
  haystack = coerce_string(haystack)
  if block
    raise ArgumentError, "wrong number of arguments (given 2, expected 1 with block)" if replacement
    fast? ? fast_gsub_with_block(haystack, &block) : stdlib_gsub_with_block(haystack, &block)
  else
    raise ArgumentError, "wrong number of arguments (given 1, expected 2)" if replacement.nil?
    replacement = coerce_string(replacement)
    if fast?
      @backend._native_gsub(haystack, replacement, literal)
    else
      haystack.gsub(@backend, stdlib_replacement(replacement, literal))
    end
  end
end

#inspectObject



147
# File 'lib/fast_regexp.rb', line 147

def inspect = "#<Fast::Regexp #{@pattern.inspect}#{stdlib? ? " (stdlib)" : ""}>"

#match(haystack) ⇒ Object



63
64
65
66
67
# File 'lib/fast_regexp.rb', line 63

def match(haystack)
  haystack = coerce_string(haystack)
  raw = fast? ? @backend._native_match(haystack) : @backend.match(haystack)
  raw && MatchData.new(raw, haystack)
end

#match?(haystack) ⇒ Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/fast_regexp.rb', line 69

def match?(haystack)
  @backend.match?(coerce_string(haystack))
end

#namesObject



143
144
145
# File 'lib/fast_regexp.rb', line 143

def names
  @backend.names
end

#nativeObject

Escape hatches for callers that need the underlying object directly.



60
# File 'lib/fast_regexp.rb', line 60

def native = fast? ? @backend : nil

#scan(haystack) ⇒ Object



86
87
88
# File 'lib/fast_regexp.rb', line 86

def scan(haystack)
  @backend.scan(coerce_string(haystack))
end

#scan_matches(haystack) ⇒ Object



90
91
92
93
94
95
96
97
98
99
# File 'lib/fast_regexp.rb', line 90

def scan_matches(haystack)
  haystack = coerce_string(haystack)
  if fast?
    @backend.scan_matches(haystack).map { |m| MatchData.new(m, haystack) }
  else
    results = []
    haystack.scan(@backend) { results << MatchData.new(::Regexp.last_match, haystack) }
    results
  end
end

#stdlibObject



61
# File 'lib/fast_regexp.rb', line 61

def stdlib = stdlib? ? @backend : nil

#stdlib?Boolean

Returns:

  • (Boolean)


57
# File 'lib/fast_regexp.rb', line 57

def stdlib? = !fast?

#sub(haystack, replacement = nil, literal: false, &block) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/fast_regexp.rb', line 101

def sub(haystack, replacement = nil, literal: false, &block)
  haystack = coerce_string(haystack)
  if block
    raise ArgumentError, "wrong number of arguments (given 2, expected 1 with block)" if replacement
    m = match(haystack)
    return haystack.dup unless m
    "#{m.pre_match}#{block.call(m)}#{m.post_match}"
  else
    raise ArgumentError, "wrong number of arguments (given 1, expected 2)" if replacement.nil?
    replacement = coerce_string(replacement)
    if fast?
      @backend._native_sub(haystack, replacement, literal)
    else
      haystack.sub(@backend, stdlib_replacement(replacement, literal))
    end
  end
end