class Range include Enumerable def initialize(first, last, exclude_end = false) raise NameError, "`initialize' called twice" if @begin unless first.kind_of?(Fixnum) && last.kind_of?(Fixnum) begin raise ArgumentError, "bad value for range" unless first <=> last rescue raise ArgumentError, "bad value for range" end end @begin = first @end = last @excl = exclude_end end private :initialize def ==(other) return true if equal? other other.kind_of?(Range) and self.first == other.first and self.last == other.last and self.exclude_end? == other.exclude_end? end alias_method :eql?, :== attr_reader_specific :excl, :exclude_end? attr_reader :begin attr_reader :end def bsearch return to_enum :bsearch unless block_given? unless @begin.kind_of? Numeric and @end.kind_of? Numeric raise TypeError, "bsearch is not available for #{@begin.class}" end min = @begin max = @end max -= 1 if max.kind_of? Integer and @excl start = min = Rubinius::Type.coerce_to min, Integer, :to_int total = max = Rubinius::Type.coerce_to max, Integer, :to_int last_true = nil seeker = Proc.new do |current| x = yield current return current if x == 0 case x when Numeric if x > 0 min = current + 1 else max = current end when true last_true = current max = current when false, nil min = current + 1 else raise TypeError, "Range#bsearch block must return Numeric or boolean" end end while min < max if max < 0 and min < 0 mid = min + (max - min) / 2 elsif min < -max mid = -((-1 - min - max) / 2 + 1) else mid = (min + max) / 2 end seeker.call mid end if min == max seeker.call min end if min < max return @begin if mid == start return @begin.kind_of?(Float) ? mid.to_f : mid end if last_true return @begin if last_true == start return @begin.kind_of?(Float) ? last_true.to_f : last_true end nil end def each return to_enum { size } unless block_given? first, last = @begin, @end unless first.respond_to?(:succ) && !first.kind_of?(Time) raise TypeError, "can't iterate from #{first.class}" end case first when Fixnum last -= 1 if @excl i = first while i <= last yield i i += 1 end when String first.upto(last, @excl) do |str| yield str end when Symbol first.to_s.upto(last.to_s, @excl) do |str| yield str.to_sym end else current = first if @excl while (current <=> last) < 0 yield current current = current.succ end else while (c = current <=> last) && c <= 0 yield current break if c == 0 current = current.succ end end end self end def first(n=undefined) return @begin if undefined.equal? n super end def hash excl = @excl ? 1 : 0 hash = excl hash ^= @begin.hash << 1 hash ^= @end.hash << 9 hash ^= excl << 24; # Are we throwing away too much here for a good hash value distribution? return hash & Fixnum::MAX end def include?(value) if @begin.respond_to?(:to_int) || @end.respond_to?(:to_int) || @begin.kind_of?(Numeric) || @end.kind_of?(Numeric) cover? value else super end end alias_method :member?, :include? def ===(value) include?(value) end def inspect "#{@begin.inspect}#{@excl ? "..." : ".."}#{@end.inspect}" end def last(n=undefined) return @end if undefined.equal? n to_a.last(n) end def max return super if block_given? || (@excl && !@end.kind_of?(Numeric)) return nil if @end < @begin || (@excl && @end == @begin) return @end unless @excl unless @end.kind_of?(Integer) raise TypeError, "cannot exclude non Integer end value" end unless @begin.kind_of?(Integer) raise TypeError, "cannot exclude end value with non Integer begin value" end @end - 1 end def min return super if block_given? return nil if @end < @begin || (@excl && @end == @begin) @begin end def step(step_size=1) # :yields: object return to_enum(:step, step_size) do m = Rubinius::Mirror::Range.reflect(self) m.step_iterations_size(*m.validate_step_size(@begin, @end, step_size)) end unless block_given? m = Rubinius::Mirror::Range.reflect(self) values = m.validate_step_size(@begin, @end, step_size) first = values[0] last = values[1] step_size = values[2] case first when Float iterations = m.step_float_iterations_size(first, last, step_size) i = 0 while i < iterations curr = i * step_size + first curr = last if last < curr yield curr i += 1 end when Numeric curr = first last -= 1 if @excl while curr <= last yield curr curr += step_size end else i = 0 each do |curr| yield curr if i % step_size == 0 i += 1 end end self end def to_s "#{@begin}#{@excl ? "..." : ".."}#{@end}" end def to_a return super unless @begin.kind_of? Fixnum and @end.kind_of? Fixnum fin = @end fin += 1 unless @excl size = fin - @begin return [] if size <= 0 ary = Array.new(size) i = 0 while i < size ary[i] = @begin + i i += 1 end ary end def cover?(value) # MRI uses <=> to compare, so must we. beg_compare = (@begin <=> value) return false unless beg_compare if Comparable.compare_int(beg_compare) <= 0 end_compare = (value <=> @end) if @excl return true if Comparable.compare_int(end_compare) < 0 else return true if Comparable.compare_int(end_compare) <= 0 end end false end def size return nil unless @begin.kind_of?(Numeric) delta = @end - @begin return 0 if delta < 0 if @begin.kind_of?(Float) || @end.kind_of?(Float) return delta if delta == Float::INFINITY err = (@begin.abs + @end.abs + delta.abs) * Float::EPSILON err = 0.5 if err > 0.5 (@excl ? delta - err : delta + err).floor + 1 else delta += 1 unless @excl delta end end end