Temporary tag for this failure. Updated CI spec coming.
[rbx.git] / kernel / core / regexp.rb
blob64c3d156102940e209472b75bb87c6fa01e5fd0d
1 # depends on: class.rb string.rb
3 class Regexp
5   ivar_as_index :__ivars__ => 0, :source => 1, :data => 2, :names => 3
6   def __ivars__; @__ivars__ ; end
7   def source   ; @source    ; end
8   def data     ; @data      ; end
9   def names    ; @names     ; end
11   ValidOptions  = ['m','i','x']
12   ValidKcode    = [?n,?e,?s,?u]
13   KcodeValue    = [16,32,48,64]
15   IGNORECASE    = 1
16   EXTENDED      = 2
17   MULTILINE     = 4
18   OPTION_MASK   = 7
20   KCODE_ASCII   = 0
21   KCODE_NONE    = 16
22   KCODE_EUC     = 32
23   KCODE_SJIS    = 48
24   KCODE_UTF8    = 64
25   KCODE_MASK    = 112
27   ##
28   # Constructs a new regular expression from the given pattern. The pattern
29   # may either be a String or a Regexp. If given a Regexp, options are copied
30   # from the pattern and any options given are not honoured. If the pattern is
31   # a String, additional options may be given.
32   #
33   # The first optional argument can either be a Fixnum representing one or
34   # more of the Regexp options ORed together (Regexp::IGNORECASE, EXTENDED and
35   # MULTILINE) or a flag to toggle case sensitivity. If opts is nil or false,
36   # the match is case sensitive. If opts is any non-nil, non-false and
37   # non-Fixnum object, its presence makes the regexp case insensitive (the obj
38   # is not used in any way.)
39   #
40   # The second optional argument can be used to enable multibyte support
41   # (which is disabled by default.) The flag must be one of the following
42   # strings in any combination of upper- and lowercase:
43   #
44   # * 'e', 'euc'  for EUC
45   # * 's', 'sjis' for SJIS
46   # * 'u', 'utf8' for UTF-8
47   #
48   # You may also explicitly pass in 'n', 'N' or 'none' to disable multibyte
49   # support. Any other values are ignored.
51   def self.new(pattern, opts = nil, lang = nil)
52     if pattern.is_a?(Regexp)
53       opts = pattern.options
54       pattern  = pattern.source
55     elsif opts.kind_of?(Fixnum)
56       opts = opts & (OPTION_MASK | KCODE_MASK) if opts > 0
57     elsif opts
58       opts = IGNORECASE
59     else
60       opts = 0
61     end
63     if opts and lang and lang.kind_of?(String)
64       opts &= OPTION_MASK
65       idx   = ValidKcode.index(lang.downcase[0])
66       opts |= KcodeValue[idx] if idx
67     end
69     if self.class.equal? Regexp
70       return __regexp_new__(pattern, opts)
71     else
72       r = __regexp_new__(pattern, opts)
73       r.send :initialize, pattern, opts, lang
74       return r
75     end
76   end
78   #--
79   # FIXME - Optimize me using String#[], String#chr, etc.
80   # Do away with the control-character comparisons.
81   #++
83   def self.escape(str)
84     meta = %w![ ] { } ( ) | - * . \\ ? + ^ $ #!
85     quoted = ""
86     str.codepoints.each do |c|
87       quoted << if meta.include?(c)
88       "\\#{c}"
89       elsif c == "\n"
90       "\\n"
91       elsif c == "\r"
92       "\\r"
93       elsif c == "\f"
94       "\\f"
95       elsif c == "\t"
96       "\\t"
97       elsif c == " "
98       "\\ "
99       else
100         c
101       end
102     end
103     quoted
104   end
106   class << self
107     alias_method :compile, :new
108     alias_method :quote, :escape
109   end
111   ##
112   # See Regexp.new. This may be overridden by subclasses.
114   def initialize(arg, opts, lang)
115     # Nothing to do
116   end
118   def self.last_match(field = nil)
119     match = MethodContext.current.sender.last_match
120     if match
121       return match if field.nil?
122       return match[field]
123     else
124       return nil
125     end
126   end
127   
128   def self.last_match=(match)
129     # Set an ivar in the sender of our sender
130     parent = MethodContext.current.sender
131     ctx = parent.sender
132     ctx.last_match = match
133   end
135   ##
136   # Different than last_match= because it sets the current last match, while
137   # last_match= sets the senders last match.
139   def self.my_last_match=(match)
140     # Set an ivar in the sender 
141     ctx = MethodContext.current.sender
142     ctx.last_match = match
143   end
145   def self.union(*patterns)
146     if patterns.nil? || patterns.length == 0
147       return /(?!)/
148     else
149       flag  = false
150       string = ""
151       patterns.each do |pattern|
152         string += '|' if flag
153         string += pattern.to_s
154         flag = true
155       end
156       return Regexp.new(string)
157     end
158   end
160   def ~
161     line = $_
162     if !line.is_a?(String)
163       Regexp.last_match = nil
164       return nil
165     end
166     res = self.match(line)
167     return res.nil? ? nil : res.begin(0)
168   end
170   # Returns the index of the first character in the region that
171   # matched or nil if there was no match. See #match for returning
172   # the MatchData instead.
173   def =~(str)
174     # unless str.nil? because it's nil and only nil, not false.
175     str = StringValue(str) unless str.nil?
177     match = match_from(str, 0)
178     if match
179       Regexp.last_match = match
180       return match.begin(0)
181     else
182       Regexp.last_match = nil
183       return nil
184     end
185   end
186   
187   def match_all(str)
188     start = 0
189     arr = []
190     while(match = self.match_from(str, start))
191       arr << match
192       if match.collapsing?
193         start += 1
194       else
195         start = match.end(0)
196       end
197     end
198     arr
199   end
201   def ===(other)
202     if !other.is_a?(String)
203       if !other.respond_to?(:to_str)
204         Regexp.last_match = nil
205         return false
206       end
207     end
208     if match = self.match_from(other.to_str, 0)
209       Regexp.last_match = match
210       return true
211     else
212       Regexp.last_match = nil
213       return false
214     end
215   end
217   def casefold?
218     (options & IGNORECASE) > 0 ? true : false
219   end
221   def eql?(other)
222     return false unless other.kind_of?(Regexp)
223     # Ruby 1.8 doesn't destinguish between KCODE_NONE (16) & not specified (0) for eql?
224     self_options  = options       & KCODE_MASK != 0 ? options       : options       + KCODE_NONE
225     other_options = other.options & KCODE_MASK != 0 ? other.options : other.options + KCODE_NONE
226     return (source == other.source) && ( self_options == other_options)
227   end
229   alias_method :==, :eql?
231   def hash
232     str = '/' << source << '/' << option_to_string(options)
233     if options & KCODE_MASK == 0
234       str << 'n'
235     else
236       str << kcode[0,1]
237     end
238     str.hash
239   end
241   def inspect
242     str = '/' << source.gsub("/", "\\/") << '/' << option_to_string(options)
243     k = kcode()
244     str << k[0,1] if k and k != "none"
245     return str
246   end
248   def kcode
249     lang = options & KCODE_MASK
250     return "none" if lang == KCODE_NONE
251     return "euc"  if lang == KCODE_EUC
252     return 'sjis' if lang == KCODE_SJIS
253     return 'utf8' if lang == KCODE_UTF8
254     return nil
255   end
257   # Performs normal match and returns MatchData object from $~ or nil.
258   def match(str)
259     return nil if str.nil?
260     Regexp.last_match = search_region(str, 0, str.size, true)
261   end
262   
263   def match_from(str, count)
264     return nil if str.nil?
265     search_region(str, count, str.size, true)
266   end
268   def to_s
269     idx     = 0
270     offset  = 0
271     pattern = source
272     option  = options
273     len     = pattern.size
274     endpt   = -1
276     loop do
277       if (len - idx) > 4 && pattern[idx,2] == "(?"
278         idx += 2
280         offset = get_option_string_length(pattern[idx..-1])
281         if offset > 0
282           option |= string_to_option(pattern[idx, offset])
283           idx += offset
284         end
286         if pattern[idx,1] == '-'
287           idx += 1
288           offset = get_option_string_length(pattern[idx..-1])
289           if offset > 0
290             option &= ~string_to_option(pattern[idx, offset])
291             idx += offset
292           end
293         end
295         if pattern[idx..1] == ')'
296           idx += 1
297           next
298         elsif pattern[idx,1] == ':' && pattern[-1,1] == ')'
299           idx += 1
300           if !Regexp.new(pattern[idx..-2], 0).is_a?(Regexp)
301             option = self.options
302             idx    = 0
303           else
304             endpt -= 1
305           end
306         end
307       end
308       break
309     end
310     string = '(?'
311     string << option_to_string(option)
312     if (option & OPTION_MASK) != OPTION_MASK
313       string << '-' << option_to_string(~option)
314     end
315     string << ':' << pattern[0..endpt] << ')'
316   end
318   def get_option_string_length(string)
319     idx = 0
320     while idx < string.length do
321       if !ValidOptions.include?(string[idx,1])
322         return idx
323       end
324       idx += 1
325     end
326     return idx
327   end
329   def option_to_string(option)
330     string = ""
331     string << 'm' if (option & MULTILINE) > 0
332     string << 'i' if (option & IGNORECASE) > 0
333     string << 'x' if (option & EXTENDED) > 0
334     return string
335   end
339 class MatchData
341   ivar_as_index :__ivars__ => 0, :source => 1, :regexp => 2, :full => 3, :region => 4
342   
343   def string
344     @source
345   end
346   
347   def source
348     @source
349   end
350   
351   def full
352     @full
353   end
354   
355   def begin(idx)
356    return full.at(0) if idx == 0
357    return @region.at(idx - 1).at(0)
358   end
360   def end(idx)
361    return full.at(1) if idx == 0
362    @region.at(idx - 1).at(1)
363   end
365   def offset(idx)
366    out = []
367    out << self.begin(idx)
368    out << self.end(idx)
369    return out
370   end
372   def length
373    @region.fields + 1
374   end
376   def captures
377     out = []
378     @region.each do |tup|
379       x = tup.at(0)
380       
381       if x == -1
382         out << nil
383       else  
384         y = tup.at(1)
385         out << @source[x, y-x]
386       end
387     end
388     return out
389   end
390   
391   def pre_match
392     return "" if full.at(0) == 0
393     nd = full.at(0) - 1
394     @source[0, nd+1]
395   end
396   
397   def pre_match_from(idx)
398     return "" if full.at(0) == 0
399     nd = full.at(0) - 1
400     @source[idx, nd-idx+1]    
401   end
402   
403   def collapsing?
404     self.begin(0) == self.end(0)
405   end
406   
407   def post_match
408     nd = @source.size - 1
409     st = full.at(1)
410     @source[st, nd-st+1]
411   end
413   def [](idx, len = nil)
414     if len
415       return to_a[idx, len]
416     elsif idx.is_a?(Symbol)
417       num = @regexp.names[idx]
418       raise ArgumentError, "Unknown named group '#{idx}'" unless num
419       return get_capture(num)
420     elsif !idx.is_a?(Integer) or idx < 0
421       return to_a[idx]
422     end
423     
424     if idx == 0
425       return matched_area()
426     elsif idx < size
427       return get_capture(idx - 1)
428     end
429   end
431   def to_s
432     matched_area()
433   end
435   def inspect
436     "#<MatchData:0x#{object_id.to_s(16)} \"#{matched_area}\">"
437   end
439   def select
440     unless block_given?
441       raise LocalJumpError, "no block given"
442     end
443     
444     out = []
445     ma = matched_area()
446     out << ma if yield ma
447     
448     each_capture do |str|
449       if yield(str)
450         out << str
451       end
452     end
453     return out
454   end
456   alias_method :size, :length
458   def to_a
459     ary = captures()
460     ary.unshift matched_area()
461     return ary
462   end
464   def values_at(*indexes)
465     indexes.map { |i| self[i] }
466   end
467   
468   def matched_area
469     x = full[0]
470     y = full[1]
471     @source[x, y-x]
472   end
473   
474   private :matched_area
476   def get_capture(num)
477     x, y = @region[num]
478     return nil if !y or x == -1
479     
480     return @source[x, y-x]
481   end
483   private :get_capture
485   def each_capture
486     @region.each do |tup|
487       x, y = *tup
488       yield @source[x, y-x]
489     end
490   end
492   private :each_capture