Module: Flexr::Unicode::ReferenceRegexp
- Defined in:
- lib/flexr/unicode/reference_regexp.rb
Constant Summary collapse
- CACHE =
rubocop:disable Style/MutableConstant
{}
Class Method Summary collapse
- .byte_class(ranges) ⇒ Object
- .byte_escape(lo, hi) ⇒ Object
- .casefold_ranges(ranges) ⇒ Object
- .codepoint_class(ranges) ⇒ Object
- .codepoint_escape(lo, hi) ⇒ Object
- .compiled(pattern, encoding:, options: nil, unicode: false) ⇒ Object
- .complement(ranges) ⇒ Object
- .match(pattern, subject, encoding:, options: 0, unicode: false) ⇒ Object
- .scalar_ranges(ranges) ⇒ Object
- .source_for(node, ignorecase: false) ⇒ Object
Class Method Details
.byte_class(ranges) ⇒ Object
60 61 62 |
# File 'lib/flexr/unicode/reference_regexp.rb', line 60 def byte_class(ranges) "[#{ranges.map { |lo, hi| byte_escape(lo, hi) }.join}]" end |
.byte_escape(lo, hi) ⇒ Object
64 65 66 |
# File 'lib/flexr/unicode/reference_regexp.rb', line 64 def byte_escape(lo, hi) lo == hi ? format("\\x%<byte>02X", byte: lo) : format("\\x%<lo>02X-\\x%<hi>02X", lo: lo, hi: hi) end |
.casefold_ranges(ranges) ⇒ Object
101 102 103 |
# File 'lib/flexr/unicode/reference_regexp.rb', line 101 def casefold_ranges(ranges) CaseFold.merge(ranges.flat_map { |lo, hi| CaseFold.ranges(lo, hi) }) end |
.codepoint_class(ranges) ⇒ Object
68 69 70 71 72 73 |
# File 'lib/flexr/unicode/reference_regexp.rb', line 68 def codepoint_class(ranges) ranges = scalar_ranges(ranges) return "(?!)" if ranges.empty? "[#{ranges.sort_by(&:first).map { |lo, hi| codepoint_escape(lo, hi) }.join}]" end |
.codepoint_escape(lo, hi) ⇒ Object
84 85 86 87 88 |
# File 'lib/flexr/unicode/reference_regexp.rb', line 84 def codepoint_escape(lo, hi) first = ::Regexp.escape([lo].pack("U")) last = ::Regexp.escape([hi].pack("U")) lo == hi ? first : "#{first}-#{last}" end |
.compiled(pattern, encoding:, options: nil, unicode: false) ⇒ Object
19 20 21 22 23 24 25 26 27 28 |
# File 'lib/flexr/unicode/reference_regexp.rb', line 19 def compiled(pattern, encoding:, options: nil, unicode: false) = .nil? ? pattern. : key = [pattern.source, , encoding, unicode] return CACHE[key] if CACHE.key?(key) parser = Regexp::Parser.new(pattern.source, options: , encoding: encoding, unicode: unicode) source = source_for(parser.parse, ignorecase: .anybits?(::Regexp::IGNORECASE)) = & ~::Regexp::IGNORECASE CACHE[key] = ::Regexp.new(source, ).freeze end |
.complement(ranges) ⇒ Object
90 91 92 93 94 95 96 97 98 99 |
# File 'lib/flexr/unicode/reference_regexp.rb', line 90 def complement(ranges) result = [] cursor = 0 ranges.sort_by(&:first).each do |lo, hi| result << [cursor, lo - 1] if cursor < lo cursor = [cursor, hi + 1].max end result << [cursor, 0x10ffff] if cursor <= 0x10ffff result end |
.match(pattern, subject, encoding:, options: 0, unicode: false) ⇒ Object
11 12 13 14 15 16 17 |
# File 'lib/flexr/unicode/reference_regexp.rb', line 11 def match(pattern, subject, encoding:, options: 0, unicode: false) regexp = compiled(pattern, encoding: encoding, options: , unicode: unicode) subject = subject.dup.force_encoding(regexp.encoding) regexp.match(subject, 0) rescue RegexpError, ArgumentError, EncodingError nil end |
.scalar_ranges(ranges) ⇒ Object
75 76 77 78 79 80 81 82 |
# File 'lib/flexr/unicode/reference_regexp.rb', line 75 def scalar_ranges(ranges) ranges.flat_map do |lo, hi| result = [] result << [lo, [hi, 0xd7ff].min] if lo <= 0xd7ff result << [[lo, 0xe000].max, hi] if hi >= 0xe000 result end end |
.source_for(node, ignorecase: false) ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/flexr/unicode/reference_regexp.rb', line 30 def source_for(node, ignorecase: false) case node when Regexp::AST::Empty, Regexp::AST::Anchor then "" when Regexp::AST::Fail then "(?!)" when Regexp::AST::ByteRange then byte_class([[node.lo, node.hi]]) when Regexp::AST::CodepointRange then codepoint_class([[node.lo, node.hi]]) when Regexp::AST::CharClass ranges = node.ranges.flat_map do |range| if range.first == Regexp::AST::Property property_ranges = Unicode::Property.ranges(range[2]) property_ranges = casefold_ranges(property_ranges) if ignorecase range[1] ? complement(property_ranges) : property_ranges else [range] end end ranges = complement(ranges) if node.negated codepoint_class(ranges) when Regexp::AST::Seq then node.children.map { |child| source_for(child, ignorecase: ignorecase) }.join when Regexp::AST::Alt "(?:#{node.children.map { |child| source_for(child, ignorecase: ignorecase) }.join('|')})" when Regexp::AST::Star then "(?:#{source_for(node.child, ignorecase: ignorecase)})*" when Regexp::AST::Repeat maximum = node.maximum.nil? ? "" : node.maximum "(?:#{source_for(node.child, ignorecase: ignorecase)}){#{node.minimum},#{maximum}}" else raise CompileError, "unsupported reference AST node: #{node.class}" end end |