core/enumerator.rb
class Enumerator
include Enumerable
attr_writer :args
private :args=
attr_writer :size
private :size=
attr_reader :generator
protected :generator
attr_reader :lookahead
protected :lookahead
def initialize_enumerator(receiver, size, method_name, *method_args)
@object = receiver
@size = size
@iter = method_name
@args = method_args
@generator = nil
@lookahead = []
@feedvalue = nil
self
end
private :initialize_enumerator
def initialize(receiver_or_size=undefined, method_name=:each, *method_args, &block)
size = nil
if block_given?
unless undefined.equal? receiver_or_size
size = if receiver_or_size.nil? || receiver_or_size == Float::INFINITY || receiver_or_size.respond_to?(:call)
receiver_or_size
else
Rubinius::Type.coerce_to receiver_or_size, Integer, :to_int
end
end
receiver = Generator.new(&block)
else
if undefined.equal? receiver_or_size
raise ArgumentError, "Enumerator#initialize requires a block when called without arguments"
end
receiver = receiver_or_size
end
method_name = Rubinius::Type.coerce_to method_name, Symbol, :to_sym
initialize_enumerator receiver, size, method_name, *method_args
self
end
private :initialize
def each(*args)
enumerator = self
new_args = @args
unless args.empty?
enumerator = dup
new_args = @args.empty? ? args : (@args + args)
end
Rubinius.privately do
enumerator.args = new_args
end
if block_given?
Rubinius.privately do
enumerator.each_with_block do |*yield_args|
yield(*yield_args)
end
end
else
enumerator
end
end
def each_with_block
@object.__send__ @iter, *@args do |*args|
ret = yield(*args)
unless @feedvalue.nil?
ret = @feedvalue
@feedvalue = nil
end
ret
end
end
private :each_with_block
def each_with_index
return to_enum(:each_with_index) { size } unless block_given?
idx = 0
each do
o = Rubinius.single_block_arg
val = yield(o, idx)
idx += 1
val
end
end
def next
return @lookahead.shift unless @lookahead.empty?
@generator ||= Iterator.new self
begin
return @generator.next if @generator.next?
rescue StopIteration
end
exception = StopIteration.new "iteration reached end"
Rubinius.privately do
exception.result = @generator.result
end
raise exception
end
def next_values
Array(self.next)
end
def peek
return @lookahead.first unless @lookahead.empty?
item = self.next
@lookahead << item
item
end
def peek_values
Array(self.peek).dup
end
def rewind
@object.rewind if @object.respond_to? :rewind
@generator.rewind if @generator
@lookahead = []
@feedvalue = nil
self
end
def size
@size.respond_to?(:call) ? @size.call : @size
end
def with_index(offset=0)
if offset
offset = Rubinius::Type.coerce_to offset, Integer, :to_int
else
offset = 0
end
return to_enum(:with_index, offset) { size } unless block_given?
each do
o = Rubinius.single_block_arg
val = yield(o, offset)
offset += 1
val
end
end
def feed(val)
raise TypeError, "Feed value already set" unless @feedvalue.nil?
@feedvalue = val
nil
end
def dup
raise TypeError, "Can't copy iteration state" if @generator&.instance_variable_get(:@fiber)
super
end
def initialize_copy(other)
@generator = other.generator&.dup
@lookahead = other.lookahead.dup
super
end
private :initialize_copy
class Yielder
def initialize(&block)
raise LocalJumpError, "Expected a block to be given" unless block_given?
@proc = block
self
end
private :initialize
def yield(*args)
@proc.call *args
end
def <<(*args)
self.yield(*args)
self
end
end
class Generator
include Enumerable
def initialize(&block)
raise LocalJumpError, "Expected a block to be given" unless block_given?
@proc = block
self
end
private :initialize
def each(*args)
enclosed_yield = Proc.new { |*enclosed_args| yield *enclosed_args }
@proc.call Yielder.new(&enclosed_yield), *args
end
end
class Lazy < self
class StopLazyError < Exception; end
def initialize(receiver, size=nil)
raise ArgumentError, "Lazy#initialize requires a block" unless block_given?
Rubinius.check_frozen
super(size) do |yielder, *each_args|
begin
receiver.each(*each_args) do |*args|
yield yielder, *args
end
rescue Exception
nil
end
end
self
end
private :initialize
def to_enum(method_name=:each, *method_args, &block)
size = block_given? ? block : nil
ret = Lazy.allocate
Rubinius.privately do
ret.initialize_enumerator self, size, method_name, *method_args
end
ret
end
alias_method :enum_for, :to_enum
def lazy
self
end
alias_method :force, :to_a
def take(n)
n = Rubinius::Type.coerce_to n, Integer, :to_int
raise ArgumentError, "attempt to take negative size" if n < 0
current_size = enumerator_size
set_size = if current_size.kind_of?(Numeric)
n < current_size ? n : current_size
else
current_size
end
return to_enum(:cycle, 0).lazy if n.zero?
taken = 0
Lazy.new(self, set_size) do |yielder, *args|
if taken < n
yielder.yield(*args)
taken += 1
raise StopLazyError unless taken < n
else
raise StopLazyError
end
end
end
def drop(n)
n = Rubinius::Type.coerce_to n, Integer, :to_int
raise ArgumentError, "attempt to drop negative size" if n < 0
current_size = enumerator_size
set_size = if current_size.kind_of?(Integer)
n < current_size ? current_size - n : 0
else
current_size
end
dropped = 0
Lazy.new(self, set_size) do |yielder, *args|
if dropped < n
dropped += 1
else
yielder.yield(*args)
end
end
end
def take_while
raise ArgumentError, "Lazy#take_while requires a block" unless block_given?
Lazy.new(self, nil) do |yielder, *args|
if yield(*args)
yielder.yield(*args)
else
raise StopLazyError
end
end
end
def drop_while
raise ArgumentError, "Lazy#drop_while requires a block" unless block_given?
succeeding = true
Lazy.new(self, nil) do |yielder, *args|
if succeeding
unless yield(*args)
succeeding = false
yielder.yield(*args)
end
else
yielder.yield(*args)
end
end
end
def select
raise ArgumentError, 'Lazy#{select,find_all} requires a block' unless block_given?
Lazy.new(self, nil) do |yielder, *args|
val = args.length >= 2 ? args : args.first
yielder.yield(*args) if yield(val)
end
end
alias_method :find_all, :select
def reject
raise ArgumentError, "Lazy#reject requires a block" unless block_given?
Lazy.new(self, nil) do |yielder, *args|
val = args.length >= 2 ? args : args.first
yielder.yield(*args) unless yield(val)
end
end
def grep(pattern)
Lazy.new(self, nil) do |yielder, *args|
val = args.length >= 2 ? args : args.first
if pattern === val
Regexp.set_block_last_match
if block_given?
yielder.yield yield(val)
else
yielder.yield val
end
end
end
end
def grep_v(pattern)
Lazy.new(self, nil) do |yielder, *args|
val = args.length >= 2 ? args : args.first
unless pattern === val
if block_given?
yielder.yield yield(val)
else
yielder.yield val
end
end
end
end
def map
raise ArgumentError, 'Lazy#{map,collect} requires a block' unless block_given?
Lazy.new(self, enumerator_size) do |yielder, *args|
yielder.yield yield(*args)
end
end
alias_method :collect, :map
def collect_concat
raise ArgumentError, 'Lazy#{collect_concat,flat_map} requires a block' unless block_given?
Lazy.new(self, nil) do |yielder, *args|
yield_ret = yield(*args)
if Rubinius::Type.object_respond_to?(yield_ret, :force) &&
Rubinius::Type.object_respond_to?(yield_ret, :each)
yield_ret.each do |v|
yielder.yield v
end
else
array = Rubinius::Type.check_convert_type yield_ret, Array, :to_ary
if array
array.each do |v|
yielder.yield v
end
else
yielder.yield yield_ret
end
end
end
end
alias_method :flat_map, :collect_concat
def zip(*lists)
return super(*lists) { |entry| yield entry } if block_given?
lists.map! do |list|
array = Rubinius::Type.check_convert_type list, Array, :to_ary
case
when array
array
when Rubinius::Type.object_respond_to?(list, :each)
list.to_enum :each
else
raise TypeError, "wrong argument type #{list.class} (must respond to :each)"
end
end
index = 0
Lazy.new(self, enumerator_size) do |yielder, *args|
val = args.length >= 2 ? args : args.first
rests = lists.map { |list|
case list
when Array
list[index]
else
begin
list.next
rescue StopIteration
nil
end
end
}
yielder.yield [val, *rests]
index += 1
end
end
end
class Iterator
STACK_SIZE = 1_048_576
attr_reader :result
def initialize(obj)
@object = obj
rewind
end
private :initialize
def next?
!@done
end
def next
reset unless @fiber
val = @fiber.resume
raise StopIteration, "iteration has ended" if @done
return val
end
def rewind
@fiber = nil
@done = false
end
def reset
@done = false
@fiber = Fiber.new stack_size: STACK_SIZE do
obj = @object
@result = obj.each { |*val| Fiber.yield *val }
@done = true
end
end
end
end