first commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
MRuby::Gem::Specification.new('mruby-enumerator') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.add_dependency('mruby-fiber', :core => 'mruby-fiber')
|
||||
spec.summary = 'Enumerator class'
|
||||
end
|
||||
@@ -0,0 +1,689 @@
|
||||
##
|
||||
# enumerator.rb Enumerator class
|
||||
# See Copyright Notice in mruby.h
|
||||
|
||||
##
|
||||
# A class which allows both internal and external iteration.
|
||||
#
|
||||
# An Enumerator can be created by the following methods.
|
||||
# - {Kernel#to_enum}
|
||||
# - {Kernel#enum_for}
|
||||
# - {Enumerator#initialize Enumerator.new}
|
||||
#
|
||||
# Most methods have two forms: a block form where the contents
|
||||
# are evaluated for each item in the enumeration, and a non-block form
|
||||
# which returns a new Enumerator wrapping the iteration.
|
||||
#
|
||||
# enumerator = %w(one two three).each
|
||||
# puts enumerator.class # => Enumerator
|
||||
#
|
||||
# enumerator.each_with_object("foo") do |item, obj|
|
||||
# puts "#{obj}: #{item}"
|
||||
# end
|
||||
#
|
||||
# # foo: one
|
||||
# # foo: two
|
||||
# # foo: three
|
||||
#
|
||||
# enum_with_obj = enumerator.each_with_object("foo")
|
||||
# puts enum_with_obj.class # => Enumerator
|
||||
#
|
||||
# enum_with_obj.each do |item, obj|
|
||||
# puts "#{obj}: #{item}"
|
||||
# end
|
||||
#
|
||||
# # foo: one
|
||||
# # foo: two
|
||||
# # foo: three
|
||||
#
|
||||
# This allows you to chain Enumerators together. For example, you
|
||||
# can map a list's elements to strings containing the index
|
||||
# and the element as a string via:
|
||||
#
|
||||
# puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
|
||||
# # => ["0:foo", "1:bar", "2:baz"]
|
||||
#
|
||||
# An Enumerator can also be used as an external iterator.
|
||||
# For example, Enumerator#next returns the next value of the iterator
|
||||
# or raises StopIteration if the Enumerator is at the end.
|
||||
#
|
||||
# e = [1,2,3].each # returns an enumerator object.
|
||||
# puts e.next # => 1
|
||||
# puts e.next # => 2
|
||||
# puts e.next # => 3
|
||||
# puts e.next # raises StopIteration
|
||||
#
|
||||
# You can use this to implement an internal iterator as follows:
|
||||
#
|
||||
# def ext_each(e)
|
||||
# while true
|
||||
# begin
|
||||
# vs = e.next_values
|
||||
# rescue StopIteration
|
||||
# return $!.result
|
||||
# end
|
||||
# y = yield(*vs)
|
||||
# e.feed y
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# o = Object.new
|
||||
#
|
||||
# def o.each
|
||||
# puts yield
|
||||
# puts yield(1)
|
||||
# puts yield(1, 2)
|
||||
# 3
|
||||
# end
|
||||
#
|
||||
# # use o.each as an internal iterator directly.
|
||||
# puts o.each {|*x| puts x; [:b, *x] }
|
||||
# # => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3
|
||||
#
|
||||
# # convert o.each to an external iterator for
|
||||
# # implementing an internal iterator.
|
||||
# puts ext_each(o.to_enum) {|*x| puts x; [:b, *x] }
|
||||
# # => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3
|
||||
#
|
||||
class Enumerator
|
||||
include Enumerable
|
||||
|
||||
##
|
||||
# @overload initialize(obj, method = :each, *args)
|
||||
#
|
||||
# Creates a new Enumerator object, which can be used as an
|
||||
# Enumerable.
|
||||
#
|
||||
# In the first form, iteration is defined by the given block, in
|
||||
# which a "yielder" object, given as block parameter, can be used to
|
||||
# yield a value by calling the +yield+ method (aliased as +<<+):
|
||||
#
|
||||
# fib = Enumerator.new do |y|
|
||||
# a = b = 1
|
||||
# loop do
|
||||
# y << a
|
||||
# a, b = b, a + b
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# p fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
|
||||
#
|
||||
# In the second, deprecated, form, a generated Enumerator iterates over the
|
||||
# given object using the given method with the given arguments passed. This
|
||||
# form is left only for internal use.
|
||||
#
|
||||
# Use of this form is discouraged. Use Kernel#enum_for or Kernel#to_enum
|
||||
# instead.
|
||||
def initialize(obj=NONE, meth=:each, *args, &block)
|
||||
if block
|
||||
obj = Generator.new(&block)
|
||||
elsif obj == NONE
|
||||
raise ArgumentError, "wrong number of arguments (given 0, expected 1+)"
|
||||
end
|
||||
|
||||
@obj = obj
|
||||
@meth = meth
|
||||
@args = args
|
||||
@fib = nil
|
||||
@dst = nil
|
||||
@lookahead = nil
|
||||
@feedvalue = nil
|
||||
@stop_exc = false
|
||||
end
|
||||
attr_accessor :obj, :meth, :args
|
||||
attr_reader :fib
|
||||
|
||||
def initialize_copy(obj)
|
||||
raise TypeError, "can't copy type #{obj.class}" unless obj.kind_of? Enumerator
|
||||
raise TypeError, "can't copy execution context" if obj.fib
|
||||
@obj = obj.obj
|
||||
@meth = obj.meth
|
||||
@args = obj.args
|
||||
@fib = nil
|
||||
@lookahead = nil
|
||||
@feedvalue = nil
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.with_index(offset = 0) {|(*args), idx| ... }
|
||||
# e.with_index(offset = 0)
|
||||
#
|
||||
# Iterates the given block for each element with an index, which
|
||||
# starts from +offset+. If no block is given, returns a new Enumerator
|
||||
# that includes the index, starting from +offset+
|
||||
#
|
||||
# +offset+:: the starting index to use
|
||||
#
|
||||
def with_index(offset=0, &block)
|
||||
return to_enum :with_index, offset unless block
|
||||
|
||||
if offset.nil?
|
||||
offset = 0
|
||||
else
|
||||
offset = offset.__to_int
|
||||
end
|
||||
|
||||
n = offset - 1
|
||||
enumerator_block_call do |*i|
|
||||
n += 1
|
||||
block.call i.__svalue, n
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.each_with_index {|(*args), idx| ... }
|
||||
# e.each_with_index
|
||||
#
|
||||
# Same as Enumerator#with_index(0), i.e. there is no starting offset.
|
||||
#
|
||||
# If no block is given, a new Enumerator is returned that includes the index.
|
||||
#
|
||||
def each_with_index(&block)
|
||||
with_index(0, &block)
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.each_with_object(obj) {|(*args), obj| ... }
|
||||
# e.each_with_object(obj)
|
||||
# e.with_object(obj) {|(*args), obj| ... }
|
||||
# e.with_object(obj)
|
||||
#
|
||||
# Iterates the given block for each element with an arbitrary object, +obj+,
|
||||
# and returns +obj+
|
||||
#
|
||||
# If no block is given, returns a new Enumerator.
|
||||
#
|
||||
# @example
|
||||
# to_three = Enumerator.new do |y|
|
||||
# 3.times do |x|
|
||||
# y << x
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# to_three_with_string = to_three.with_object("foo")
|
||||
# to_three_with_string.each do |x,string|
|
||||
# puts "#{string}: #{x}"
|
||||
# end
|
||||
#
|
||||
# # => foo:0
|
||||
# # => foo:1
|
||||
# # => foo:2
|
||||
#
|
||||
def with_object(object, &block)
|
||||
return to_enum(:with_object, object) unless block
|
||||
|
||||
enumerator_block_call do |i|
|
||||
block.call [i,object]
|
||||
end
|
||||
object
|
||||
end
|
||||
|
||||
def inspect
|
||||
if @args && @args.size > 0
|
||||
args = @args.join(", ")
|
||||
"#<#{self.class}: #{@obj.inspect}:#{@meth}(#{args})>"
|
||||
else
|
||||
"#<#{self.class}: #{@obj.inspect}:#{@meth}>"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.each { |elm| block } -> obj
|
||||
# enum.each -> enum
|
||||
# enum.each(*appending_args) { |elm| block } -> obj
|
||||
# enum.each(*appending_args) -> an_enumerator
|
||||
#
|
||||
# Iterates over the block according to how this Enumerator was constructed.
|
||||
# If no block and no arguments are given, returns self.
|
||||
#
|
||||
# === Examples
|
||||
#
|
||||
# Array.new(3) #=> [nil, nil, nil]
|
||||
# Array.new(3) { |i| i } #=> [0, 1, 2]
|
||||
# Array.to_enum(:new, 3).to_a #=> [0, 1, 2]
|
||||
# Array.to_enum(:new).each(3).to_a #=> [0, 1, 2]
|
||||
#
|
||||
# obj = Object.new
|
||||
#
|
||||
# def obj.each_arg(a, b=:b, *rest)
|
||||
# yield a
|
||||
# yield b
|
||||
# yield rest
|
||||
# :method_returned
|
||||
# end
|
||||
#
|
||||
# enum = obj.to_enum :each_arg, :a, :x
|
||||
#
|
||||
# enum.each.to_a #=> [:a, :x, []]
|
||||
# enum.each.equal?(enum) #=> true
|
||||
# enum.each { |elm| elm } #=> :method_returned
|
||||
#
|
||||
# enum.each(:y, :z).to_a #=> [:a, :x, [:y, :z]]
|
||||
# enum.each(:y, :z).equal?(enum) #=> false
|
||||
# enum.each(:y, :z) { |elm| elm } #=> :method_returned
|
||||
#
|
||||
def each(*argv, &block)
|
||||
obj = self
|
||||
if 0 < argv.length
|
||||
obj = self.dup
|
||||
args = obj.args
|
||||
if !args.empty?
|
||||
args = args.dup
|
||||
args.concat argv
|
||||
else
|
||||
args = argv.dup
|
||||
end
|
||||
obj.args = args
|
||||
end
|
||||
return obj unless block
|
||||
enumerator_block_call(&block)
|
||||
end
|
||||
|
||||
def enumerator_block_call(&block)
|
||||
@obj.__send__ @meth, *@args, &block
|
||||
end
|
||||
private :enumerator_block_call
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.next -> object
|
||||
#
|
||||
# Returns the next object in the enumerator, and move the internal position
|
||||
# forward. When the position reached at the end, StopIteration is raised.
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
# a = [1,2,3]
|
||||
# e = a.to_enum
|
||||
# p e.next #=> 1
|
||||
# p e.next #=> 2
|
||||
# p e.next #=> 3
|
||||
# p e.next #raises StopIteration
|
||||
#
|
||||
# Note that enumeration sequence by +next+ does not affect other non-external
|
||||
# enumeration methods, unless the underlying iteration methods itself has
|
||||
# side-effect
|
||||
#
|
||||
def next
|
||||
next_values.__svalue
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.next_values -> array
|
||||
#
|
||||
# Returns the next object as an array in the enumerator, and move the
|
||||
# internal position forward. When the position reached at the end,
|
||||
# StopIteration is raised.
|
||||
#
|
||||
# This method can be used to distinguish <code>yield</code> and <code>yield
|
||||
# nil</code>.
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
# o = Object.new
|
||||
# def o.each
|
||||
# yield
|
||||
# yield 1
|
||||
# yield 1, 2
|
||||
# yield nil
|
||||
# yield [1, 2]
|
||||
# end
|
||||
# e = o.to_enum
|
||||
# p e.next_values
|
||||
# p e.next_values
|
||||
# p e.next_values
|
||||
# p e.next_values
|
||||
# p e.next_values
|
||||
# e = o.to_enum
|
||||
# p e.next
|
||||
# p e.next
|
||||
# p e.next
|
||||
# p e.next
|
||||
# p e.next
|
||||
#
|
||||
# ## yield args next_values next
|
||||
# # yield [] nil
|
||||
# # yield 1 [1] 1
|
||||
# # yield 1, 2 [1, 2] [1, 2]
|
||||
# # yield nil [nil] nil
|
||||
# # yield [1, 2] [[1, 2]] [1, 2]
|
||||
#
|
||||
# Note that +next_values+ does not affect other non-external enumeration
|
||||
# methods unless underlying iteration method itself has side-effect
|
||||
#
|
||||
def next_values
|
||||
if @lookahead
|
||||
vs = @lookahead
|
||||
@lookahead = nil
|
||||
return vs
|
||||
end
|
||||
raise @stop_exc if @stop_exc
|
||||
|
||||
curr = Fiber.current
|
||||
|
||||
if !@fib || !@fib.alive?
|
||||
@dst = curr
|
||||
@fib = Fiber.new do
|
||||
result = each do |*args|
|
||||
feedvalue = nil
|
||||
Fiber.yield args
|
||||
if @feedvalue
|
||||
feedvalue = @feedvalue
|
||||
@feedvalue = nil
|
||||
end
|
||||
feedvalue
|
||||
end
|
||||
@stop_exc = StopIteration.new "iteration reached an end"
|
||||
@stop_exc.result = result
|
||||
Fiber.yield nil
|
||||
end
|
||||
@lookahead = nil
|
||||
end
|
||||
|
||||
vs = @fib.resume curr
|
||||
if @stop_exc
|
||||
@fib = nil
|
||||
@dst = nil
|
||||
@lookahead = nil
|
||||
@feedvalue = nil
|
||||
raise @stop_exc
|
||||
end
|
||||
vs
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.peek -> object
|
||||
#
|
||||
# Returns the next object in the enumerator, but doesn't move the internal
|
||||
# position forward. If the position is already at the end, StopIteration
|
||||
# is raised.
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
# a = [1,2,3]
|
||||
# e = a.to_enum
|
||||
# p e.next #=> 1
|
||||
# p e.peek #=> 2
|
||||
# p e.peek #=> 2
|
||||
# p e.peek #=> 2
|
||||
# p e.next #=> 2
|
||||
# p e.next #=> 3
|
||||
# p e.next #raises StopIteration
|
||||
#
|
||||
def peek
|
||||
peek_values.__svalue
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.peek_values -> array
|
||||
#
|
||||
# Returns the next object as an array, similar to Enumerator#next_values, but
|
||||
# doesn't move the internal position forward. If the position is already at
|
||||
# the end, StopIteration is raised.
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
# o = Object.new
|
||||
# def o.each
|
||||
# yield
|
||||
# yield 1
|
||||
# yield 1, 2
|
||||
# end
|
||||
# e = o.to_enum
|
||||
# p e.peek_values #=> []
|
||||
# e.next
|
||||
# p e.peek_values #=> [1]
|
||||
# p e.peek_values #=> [1]
|
||||
# e.next
|
||||
# p e.peek_values #=> [1, 2]
|
||||
# e.next
|
||||
# p e.peek_values # raises StopIteration
|
||||
#
|
||||
def peek_values
|
||||
if @lookahead.nil?
|
||||
@lookahead = next_values
|
||||
end
|
||||
@lookahead.dup
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.rewind -> e
|
||||
#
|
||||
# Rewinds the enumeration sequence to the beginning.
|
||||
#
|
||||
# If the enclosed object responds to a "rewind" method, it is called.
|
||||
#
|
||||
def rewind
|
||||
@obj.rewind if @obj.respond_to? :rewind
|
||||
@fib = nil
|
||||
@dst = nil
|
||||
@lookahead = nil
|
||||
@feedvalue = nil
|
||||
@stop_exc = false
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.feed obj -> nil
|
||||
#
|
||||
# Sets the value to be returned by the next yield inside +e+.
|
||||
#
|
||||
# If the value is not set, the yield returns nil.
|
||||
#
|
||||
# This value is cleared after being yielded.
|
||||
#
|
||||
# # Array#map passes the array's elements to "yield" and collects the
|
||||
# # results of "yield" as an array.
|
||||
# # Following example shows that "next" returns the passed elements and
|
||||
# # values passed to "feed" are collected as an array which can be
|
||||
# # obtained by StopIteration#result.
|
||||
# e = [1,2,3].map
|
||||
# p e.next #=> 1
|
||||
# e.feed "a"
|
||||
# p e.next #=> 2
|
||||
# e.feed "b"
|
||||
# p e.next #=> 3
|
||||
# e.feed "c"
|
||||
# begin
|
||||
# e.next
|
||||
# rescue StopIteration
|
||||
# p $!.result #=> ["a", "b", "c"]
|
||||
# end
|
||||
#
|
||||
# o = Object.new
|
||||
# def o.each
|
||||
# x = yield # (2) blocks
|
||||
# p x # (5) => "foo"
|
||||
# x = yield # (6) blocks
|
||||
# p x # (8) => nil
|
||||
# x = yield # (9) blocks
|
||||
# p x # not reached w/o another e.next
|
||||
# end
|
||||
#
|
||||
# e = o.to_enum
|
||||
# e.next # (1)
|
||||
# e.feed "foo" # (3)
|
||||
# e.next # (4)
|
||||
# e.next # (7)
|
||||
# # (10)
|
||||
#
|
||||
def feed(value)
|
||||
raise TypeError, "feed value already set" if @feedvalue
|
||||
@feedvalue = value
|
||||
nil
|
||||
end
|
||||
|
||||
# just for internal
|
||||
class Generator
|
||||
include Enumerable
|
||||
def initialize(&block)
|
||||
raise TypeError, "wrong argument type #{self.class} (expected Proc)" unless block.kind_of? Proc
|
||||
|
||||
@proc = block
|
||||
end
|
||||
|
||||
def each(*args, &block)
|
||||
args.unshift Yielder.new(&block)
|
||||
@proc.call(*args)
|
||||
end
|
||||
end
|
||||
|
||||
# just for internal
|
||||
class Yielder
|
||||
def initialize(&block)
|
||||
raise LocalJumpError, "no block given" unless block
|
||||
|
||||
@proc = block
|
||||
end
|
||||
|
||||
def yield(*args)
|
||||
@proc.call(*args)
|
||||
end
|
||||
|
||||
def << *args
|
||||
self.yield(*args)
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# Enumerator.produce(initial = nil) { |val| } -> enumerator
|
||||
#
|
||||
# Creates an infinite enumerator from any block, just called over and
|
||||
# over. Result of the previous iteration is passed to the next one.
|
||||
# If +initial+ is provided, it is passed to the first iteration, and
|
||||
# becomes the first element of the enumerator; if it is not provided,
|
||||
# first iteration receives +nil+, and its result becomes first
|
||||
# element of the iterator.
|
||||
#
|
||||
# Raising StopIteration from the block stops an iteration.
|
||||
#
|
||||
# Examples of usage:
|
||||
#
|
||||
# Enumerator.produce(1, &:succ) # => enumerator of 1, 2, 3, 4, ....
|
||||
#
|
||||
# Enumerator.produce { rand(10) } # => infinite random number sequence
|
||||
#
|
||||
# ancestors = Enumerator.produce(node) { |prev| node = prev.parent or raise StopIteration }
|
||||
# enclosing_section = ancestors.find { |n| n.type == :section }
|
||||
def Enumerator.produce(init=NONE, &block)
|
||||
raise ArgumentError, "no block given" if block.nil?
|
||||
Enumerator.new do |y|
|
||||
if init == NONE
|
||||
val = nil
|
||||
else
|
||||
val = init
|
||||
y.yield(val)
|
||||
end
|
||||
begin
|
||||
while true
|
||||
y.yield(val = block.call(val))
|
||||
end
|
||||
rescue StopIteration
|
||||
# do nothing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Kernel
|
||||
##
|
||||
# call-seq:
|
||||
# obj.to_enum(method = :each, *args) -> enum
|
||||
# obj.enum_for(method = :each, *args) -> enum
|
||||
#
|
||||
# Creates a new Enumerator which will enumerate by calling +method+ on
|
||||
# +obj+, passing +args+ if any.
|
||||
#
|
||||
# === Examples
|
||||
#
|
||||
# str = "xyz"
|
||||
#
|
||||
# enum = str.enum_for(:each_byte)
|
||||
# enum.each { |b| puts b }
|
||||
# # => 120
|
||||
# # => 121
|
||||
# # => 122
|
||||
#
|
||||
# # protect an array from being modified by some_method
|
||||
# a = [1, 2, 3]
|
||||
# some_method(a.to_enum)
|
||||
#
|
||||
# It is typical to call to_enum when defining methods for
|
||||
# a generic Enumerable, in case no block is passed.
|
||||
#
|
||||
# Here is such an example with parameter passing:
|
||||
#
|
||||
# module Enumerable
|
||||
# # a generic method to repeat the values of any enumerable
|
||||
# def repeat(n)
|
||||
# raise ArgumentError, "#{n} is negative!" if n < 0
|
||||
# unless block_given?
|
||||
# return to_enum(__method__, n) # __method__ is :repeat here
|
||||
# end
|
||||
# each do |*val|
|
||||
# n.times { yield *val }
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# %i[hello world].repeat(2) { |w| puts w }
|
||||
# # => Prints 'hello', 'hello', 'world', 'world'
|
||||
# enum = (1..14).repeat(3)
|
||||
# # => returns an Enumerator when called without a block
|
||||
# enum.first(4) # => [1, 1, 1, 2]
|
||||
#
|
||||
def to_enum(meth=:each, *args)
|
||||
Enumerator.new self, meth, *args
|
||||
end
|
||||
alias enum_for to_enum
|
||||
end
|
||||
|
||||
module Enumerable
|
||||
# use Enumerator to use infinite sequence
|
||||
def zip(*args, &block)
|
||||
args = args.map do |a|
|
||||
if a.respond_to?(:each)
|
||||
a.to_enum(:each)
|
||||
else
|
||||
raise TypeError, "wrong argument type #{a.class} (must respond to :each)"
|
||||
end
|
||||
end
|
||||
|
||||
result = block ? nil : []
|
||||
|
||||
each do |*val|
|
||||
tmp = [val.__svalue]
|
||||
args.each do |arg|
|
||||
v = if arg.nil?
|
||||
nil
|
||||
else
|
||||
begin
|
||||
arg.next
|
||||
rescue StopIteration
|
||||
nil
|
||||
end
|
||||
end
|
||||
tmp.push(v)
|
||||
end
|
||||
if result.nil?
|
||||
block.call(tmp)
|
||||
else
|
||||
result.push(tmp)
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,600 @@
|
||||
@obj = Object.new
|
||||
class << @obj
|
||||
include Enumerable
|
||||
def foo *a
|
||||
a.each { |x| yield x }
|
||||
end
|
||||
end
|
||||
|
||||
def assert_take(exp, enumerator)
|
||||
result = []
|
||||
n = exp.size
|
||||
enumerator.each do |v|
|
||||
result << v
|
||||
n -= 1
|
||||
break if n == 0
|
||||
end if n > 0
|
||||
assert_equal exp, result
|
||||
end
|
||||
|
||||
assert 'Enumerator.class' do
|
||||
assert_equal Class, Enumerator.class
|
||||
end
|
||||
|
||||
assert 'Enumerator.superclass' do
|
||||
assert_equal Object, Enumerator.superclass
|
||||
end
|
||||
|
||||
assert 'Enumerator.new' do
|
||||
assert_equal [0,1,2], 3.times.map{|i| i}.sort
|
||||
assert_equal [:x,:y,:z], [:x,:y,:z].each.map{|i| i}.sort
|
||||
assert_equal [[:x,1],[:y,2]], {x:1, y:2}.each.map{|i| i}.sort
|
||||
assert_equal [1,2,3], @obj.to_enum(:foo, 1,2,3).to_a
|
||||
assert_take [1,2,3], Enumerator.new { |y| i = 0; loop { y << (i += 1) } }
|
||||
assert_raise(ArgumentError) { Enumerator.new }
|
||||
|
||||
# examples
|
||||
fib = Enumerator.new do |y|
|
||||
a = b = 1
|
||||
loop do
|
||||
y << a
|
||||
a, b = b, a + b
|
||||
end
|
||||
end
|
||||
assert_take [1,1,2,3,5,8,13,21,34,55], fib
|
||||
end
|
||||
|
||||
assert 'Enumerator#initialize_copy' do
|
||||
assert_equal [1, 2, 3], @obj.to_enum(:foo, 1, 2, 3).dup.to_a
|
||||
e = @obj.to_enum :foo, 1, 2, 3
|
||||
assert_nothing_raised { assert_equal(1, e.next) }
|
||||
assert_raise(TypeError) { e.dup }
|
||||
|
||||
e = Enumerator.new { |y| i = 0; loop { y << (i += 1) } }.dup
|
||||
assert_nothing_raised { assert_equal(1, e.next) }
|
||||
assert_raise(TypeError) { e.dup }
|
||||
end
|
||||
|
||||
assert 'Enumerator#with_index' do
|
||||
assert_equal([[1,0],[2,1],[3,2]], @obj.to_enum(:foo, 1, 2, 3).with_index.to_a)
|
||||
assert_equal([[1,5],[2,6],[3,7]], @obj.to_enum(:foo, 1, 2, 3).with_index(5).to_a)
|
||||
a = []
|
||||
@obj.to_enum(:foo, 1, 2, 3).with_index(10).with_index(20) { |*i| a << i }
|
||||
assert_equal [[[1, 10], 20], [[2, 11], 21], [[3, 12], 22]], a
|
||||
end
|
||||
|
||||
assert 'Enumerator#with_index string offset' do
|
||||
assert_raise(TypeError){ @obj.to_enum(:foo, 1, 2, 3).with_index('1').to_a }
|
||||
end
|
||||
|
||||
assert 'Enumerator#each_with_index' do
|
||||
assert_equal([[1,0],[2,1],[3,2]], @obj.to_enum(:foo, 1, 2, 3).each_with_index.to_a)
|
||||
a = []
|
||||
@obj.to_enum(:foo, 1, 2, 3).each_with_index {|*i| a << i}
|
||||
assert_equal([[1, 0], [2, 1], [3, 2]], a)
|
||||
end
|
||||
|
||||
assert 'Enumerator#with_object' do
|
||||
obj = [0, 1]
|
||||
ret = (1..10).each.with_object(obj) {|i, memo|
|
||||
memo[0] += i
|
||||
memo[1] *= i
|
||||
}
|
||||
assert_true(obj.equal?(ret))
|
||||
assert_equal([55, 3628800], ret)
|
||||
end
|
||||
|
||||
assert 'Enumerator#with_object arguments' do
|
||||
to_three = Enumerator.new do |y|
|
||||
3.times do |x|
|
||||
y << x
|
||||
end
|
||||
end
|
||||
|
||||
a = []
|
||||
to_three_with_string = to_three.with_object("foo")
|
||||
to_three_with_string.each do |x,string|
|
||||
a << "#{string}:#{x}"
|
||||
end
|
||||
assert_equal ["foo:0","foo:1","foo:2"], a
|
||||
end
|
||||
|
||||
assert 'Enumerator#inspect' do
|
||||
e = (0..10).each
|
||||
assert_equal('#<Enumerator: 0..10:each>', e.inspect)
|
||||
e = 'FooObject'.enum_for(:foo, 1)
|
||||
assert_equal('#<Enumerator: "FooObject":foo(1)>', e.inspect)
|
||||
e = 'FooObject'.enum_for(:foo, 1, 2, 3)
|
||||
assert_equal('#<Enumerator: "FooObject":foo(1, 2, 3)>', e.inspect)
|
||||
e = nil.enum_for(:to_s)
|
||||
assert_equal('#<Enumerator: nil:to_s>', e.inspect)
|
||||
end
|
||||
|
||||
assert 'Enumerator#each' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << 1
|
||||
yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum.each(ary)
|
||||
e.next
|
||||
assert_equal([1], ary)
|
||||
end
|
||||
|
||||
assert 'Enumerator#each arguments' do
|
||||
obj = Object.new
|
||||
|
||||
def obj.each_arg(a, b=:b, *rest)
|
||||
yield a
|
||||
yield b
|
||||
yield rest
|
||||
:method_returned
|
||||
end
|
||||
|
||||
enum = obj.to_enum :each_arg, :a, :x
|
||||
|
||||
assert_equal [:a, :x, []], enum.each.to_a
|
||||
assert_true enum.each.equal?(enum)
|
||||
assert_equal :method_returned, enum.each { |elm| elm }
|
||||
|
||||
assert_equal [:a, :x, [:y, :z]], enum.each(:y, :z).to_a
|
||||
assert_false enum.each(:y, :z).equal?(enum)
|
||||
assert_equal :method_returned, enum.each(:y, :z) { |elm| elm }
|
||||
end
|
||||
|
||||
assert 'Enumerator#next' do
|
||||
e = 3.times
|
||||
3.times { |i|
|
||||
assert_equal i, e.next
|
||||
}
|
||||
assert_raise(StopIteration) { e.next }
|
||||
end
|
||||
|
||||
assert 'Enumerator#next_values' do
|
||||
o = Object.new
|
||||
def o.each
|
||||
yield
|
||||
yield 1
|
||||
yield 1, 2
|
||||
end
|
||||
e = o.to_enum
|
||||
assert_equal nil, e.next
|
||||
assert_equal 1, e.next
|
||||
assert_equal [1,2], e.next
|
||||
e = o.to_enum
|
||||
assert_equal [], e.next_values
|
||||
assert_equal [1], e.next_values
|
||||
assert_equal [1,2], e.next_values
|
||||
end
|
||||
|
||||
assert 'Enumerator#peek' do
|
||||
a = [1]
|
||||
e = a.each
|
||||
assert_equal 1, e.peek
|
||||
assert_equal 1, e.peek
|
||||
assert_equal 1, e.next
|
||||
assert_raise(StopIteration) { e.peek }
|
||||
assert_raise(StopIteration) { e.peek }
|
||||
end
|
||||
|
||||
assert 'Enumerator#peek modify' do
|
||||
o = Object.new
|
||||
def o.each
|
||||
yield 1,2
|
||||
end
|
||||
e = o.to_enum
|
||||
a = e.peek
|
||||
a << 3
|
||||
assert_equal([1,2], e.peek)
|
||||
end
|
||||
|
||||
assert 'Enumerator#peek_values' do
|
||||
o = Object.new
|
||||
def o.each
|
||||
yield
|
||||
yield 1
|
||||
yield 1, 2
|
||||
end
|
||||
e = o.to_enum
|
||||
assert_equal nil, e.peek
|
||||
assert_equal nil, e.next
|
||||
assert_equal 1, e.peek
|
||||
assert_equal 1, e.next
|
||||
assert_equal [1,2], e.peek
|
||||
assert_equal [1,2], e.next
|
||||
e = o.to_enum
|
||||
assert_equal [], e.peek_values
|
||||
assert_equal [], e.next_values
|
||||
assert_equal [1], e.peek_values
|
||||
assert_equal [1], e.next_values
|
||||
assert_equal [1,2], e.peek_values
|
||||
assert_equal [1,2], e.next_values
|
||||
e = o.to_enum
|
||||
assert_equal [], e.peek_values
|
||||
assert_equal nil, e.next
|
||||
assert_equal [1], e.peek_values
|
||||
assert_equal 1, e.next
|
||||
assert_equal [1,2], e.peek_values
|
||||
assert_equal [1,2], e.next
|
||||
e = o.to_enum
|
||||
assert_equal nil, e.peek
|
||||
assert_equal [], e.next_values
|
||||
assert_equal 1, e.peek
|
||||
assert_equal [1], e.next_values
|
||||
assert_equal [1,2], e.peek
|
||||
assert_equal [1,2], e.next_values
|
||||
end
|
||||
|
||||
assert 'Enumerator#peek_values modify' do
|
||||
o = Object.new
|
||||
def o.each
|
||||
yield 1,2
|
||||
end
|
||||
e = o.to_enum
|
||||
a = e.peek_values
|
||||
a << 3
|
||||
assert_equal [1,2], e.peek
|
||||
end
|
||||
|
||||
assert 'Enumerator#feed' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum :each, ary
|
||||
e.next
|
||||
e.feed 1
|
||||
e.next
|
||||
e.feed 2
|
||||
e.next
|
||||
e.feed 3
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_equal [1,2,3], ary
|
||||
end
|
||||
|
||||
assert 'Enumerator#feed mixed' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum :each, ary
|
||||
e.next
|
||||
e.feed 1
|
||||
e.next
|
||||
e.next
|
||||
e.feed 3
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_equal [1,nil,3], ary
|
||||
end
|
||||
|
||||
assert 'Enumerator#feed twice' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum :each, ary
|
||||
e.feed 1
|
||||
assert_raise(TypeError) { e.feed 2 }
|
||||
end
|
||||
|
||||
assert 'Enumerator#feed before first next' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum :each, ary
|
||||
e.feed 1
|
||||
e.next
|
||||
e.next
|
||||
assert_equal [1], ary
|
||||
end
|
||||
|
||||
assert 'Enumerator#feed yielder' do
|
||||
x = nil
|
||||
e = Enumerator.new {|y| x = y.yield; 10 }
|
||||
e.next
|
||||
e.feed 100
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_equal 100, x
|
||||
end
|
||||
|
||||
assert 'Enumerator#rewind' do
|
||||
e = @obj.to_enum(:foo, 1, 2, 3)
|
||||
assert_equal 1, e.next
|
||||
assert_equal 2, e.next
|
||||
e.rewind
|
||||
assert_equal 1, e.next
|
||||
assert_equal 2, e.next
|
||||
assert_equal 3, e.next
|
||||
assert_raise(StopIteration) { e.next }
|
||||
end
|
||||
|
||||
assert 'Enumerator#rewind clear feed' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum(:each, ary)
|
||||
e.next
|
||||
e.feed 1
|
||||
e.next
|
||||
e.feed 2
|
||||
e.rewind
|
||||
e.next
|
||||
e.next
|
||||
assert_equal([1,nil], ary)
|
||||
end
|
||||
|
||||
assert 'Enumerator#rewind clear' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum :each, ary
|
||||
e.next
|
||||
e.feed 1
|
||||
e.next
|
||||
e.feed 2
|
||||
e.rewind
|
||||
e.next
|
||||
e.next
|
||||
assert_equal [1,nil], ary
|
||||
end
|
||||
|
||||
assert 'Enumerator::Generator' do
|
||||
# note: Enumerator::Generator is a class just for internal
|
||||
g = Enumerator::Generator.new {|y| y << 1 << 2 << 3; :foo }
|
||||
g2 = g.dup
|
||||
a = []
|
||||
assert_equal(:foo, g.each {|x| a << x })
|
||||
assert_equal([1, 2, 3], a)
|
||||
a = []
|
||||
assert_equal(:foo, g2.each {|x| a << x })
|
||||
assert_equal([1, 2, 3], a)
|
||||
end
|
||||
|
||||
assert 'Enumerator::Generator args' do
|
||||
g = Enumerator::Generator.new {|y, x| y << 1 << 2 << 3; x }
|
||||
a = []
|
||||
assert_equal(:bar, g.each(:bar) {|x| a << x })
|
||||
assert_equal([1, 2, 3], a)
|
||||
end
|
||||
|
||||
assert 'Enumerator::Yielder' do
|
||||
# note: Enumerator::Yielder is a class just for internal
|
||||
a = []
|
||||
y = Enumerator::Yielder.new {|x| a << x }
|
||||
assert_equal(y, y << 1 << 2 << 3)
|
||||
assert_equal([1, 2, 3], a)
|
||||
|
||||
a = []
|
||||
y = Enumerator::Yielder.new {|x| a << x }
|
||||
assert_equal([1], y.yield(1))
|
||||
assert_equal([1, 2], y.yield(2))
|
||||
assert_equal([1, 2, 3], y.yield(3))
|
||||
|
||||
assert_raise(LocalJumpError) { Enumerator::Yielder.new }
|
||||
end
|
||||
|
||||
assert 'next after StopIteration' do
|
||||
a = [1]
|
||||
e = a.each
|
||||
assert_equal(1, e.next)
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_raise(StopIteration) { e.next }
|
||||
e.rewind
|
||||
assert_equal(1, e.next)
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_raise(StopIteration) { e.next }
|
||||
end
|
||||
|
||||
assert 'gc' do
|
||||
assert_nothing_raised do
|
||||
1.times do
|
||||
foo = [1,2,3].to_enum
|
||||
GC.start
|
||||
end
|
||||
GC.start
|
||||
end
|
||||
end
|
||||
|
||||
assert 'nested iteration' do
|
||||
def (o = Object.new).each
|
||||
yield :ok1
|
||||
yield [:ok2, :x].each.next
|
||||
end
|
||||
e = o.to_enum
|
||||
assert_equal :ok1, e.next
|
||||
assert_equal :ok2, e.next
|
||||
assert_raise(StopIteration) { e.next }
|
||||
end
|
||||
|
||||
assert 'Kernel#to_enum' do
|
||||
e = nil
|
||||
assert_equal Enumerator, [].to_enum.class
|
||||
assert_nothing_raised { e = [].to_enum(:_not_implemented_) }
|
||||
assert_raise(NoMethodError) { e.first }
|
||||
end
|
||||
|
||||
assert 'modifying existing methods' do
|
||||
assert_equal Enumerator, loop.class
|
||||
e = 3.times
|
||||
i = 0
|
||||
loop_ret = loop {
|
||||
assert_equal i, e.next
|
||||
i += 1
|
||||
}
|
||||
end
|
||||
|
||||
assert 'Integral#times' do
|
||||
a = 3
|
||||
b = a.times
|
||||
c = []
|
||||
b.with_object(c) do |i, obj|
|
||||
obj << i
|
||||
end
|
||||
assert_equal 3, a
|
||||
assert_equal Enumerator, b.class
|
||||
assert_equal [0,1,2], c
|
||||
end
|
||||
|
||||
assert 'Enumerable#each_with_index' do
|
||||
assert_equal [['a',0],['b',1],['c',2]], ['a','b','c'].each_with_index.to_a
|
||||
end
|
||||
|
||||
assert 'Enumerable#map' do
|
||||
a = [1,2,3]
|
||||
b = a.map
|
||||
c = b.with_index do |i, index|
|
||||
[i*i, index*index]
|
||||
end
|
||||
assert_equal [1,2,3], a
|
||||
assert_equal [[1,0],[4,1],[9,4]], c
|
||||
end
|
||||
|
||||
assert 'Enumerable#find_all' do
|
||||
assert_equal [[3,4]], [[1,2],[3,4],[5,6]].find_all.each{ |i| i[1] == 4 }
|
||||
end
|
||||
|
||||
assert 'Array#each_index' do
|
||||
a = [1,2,3]
|
||||
b = a.each_index
|
||||
c = []
|
||||
b.with_index do |index1,index2|
|
||||
c << [index1+2,index2+5]
|
||||
end
|
||||
assert_equal [1,2,3], a
|
||||
assert_equal [[2,5],[3,6],[4,7]], c
|
||||
end
|
||||
|
||||
assert 'Array#map!' do
|
||||
a = [1,2,3]
|
||||
b = a.map!
|
||||
b.with_index do |i, index|
|
||||
[i*i, index*index]
|
||||
end
|
||||
assert_equal [[1,0],[4,1],[9,4]], a
|
||||
end
|
||||
|
||||
assert 'Hash#each' do
|
||||
a = {a:1,b:2}
|
||||
b = a.each
|
||||
c = []
|
||||
b.each do |k,v|
|
||||
c << [k,v]
|
||||
end
|
||||
assert_equal [[:a,1], [:b,2]], c.sort
|
||||
end
|
||||
|
||||
assert 'Hash#each_key' do
|
||||
assert_equal [:a,:b], {a:1,b:2}.each_key.to_a.sort
|
||||
end
|
||||
|
||||
assert 'Hash#each_value' do
|
||||
assert_equal [1,2], {a:1,b:2}.each_value.to_a.sort
|
||||
end
|
||||
|
||||
assert 'Hash#select' do
|
||||
h = {1=>2,3=>4,5=>6}
|
||||
hret = h.select.with_index {|a,_b| a[1] == 4}
|
||||
assert_equal({3=>4}, hret)
|
||||
assert_equal({1=>2,3=>4,5=>6}, h)
|
||||
end
|
||||
|
||||
assert 'Hash#select!' do
|
||||
h = {1=>2,3=>4,5=>6}
|
||||
hret = h.select!.with_index {|a,_b| a[1] == 4}
|
||||
assert_equal h, hret
|
||||
assert_equal({3=>4}, h)
|
||||
end
|
||||
|
||||
assert 'Hash#reject' do
|
||||
h = {1=>2,3=>4,5=>6}
|
||||
hret = h.reject.with_index {|a,_b| a[1] == 4}
|
||||
assert_equal({1=>2,5=>6}, hret)
|
||||
assert_equal({1=>2,3=>4,5=>6}, h)
|
||||
end
|
||||
|
||||
assert 'Hash#reject!' do
|
||||
h = {1=>2,3=>4,5=>6}
|
||||
hret = h.reject!.with_index {|a,_b| a[1] == 4}
|
||||
assert_equal h, hret
|
||||
assert_equal({1=>2,5=>6}, h)
|
||||
end
|
||||
|
||||
assert 'Range#each' do
|
||||
a = (1..5)
|
||||
b = a.each
|
||||
c = []
|
||||
b.each do |i|
|
||||
c << i
|
||||
end
|
||||
assert_equal [1,2,3,4,5], c
|
||||
end
|
||||
|
||||
assert 'Enumerable#zip' do
|
||||
assert_equal [[1, 10], [2, 11], [3, 12]], [1,2,3].zip(10..Float::INFINITY)
|
||||
|
||||
ret = []
|
||||
assert_equal nil, [1,2,3].zip(10..Float::INFINITY) { |i| ret << i }
|
||||
assert_equal [[1, 10], [2, 11], [3, 12]], ret
|
||||
|
||||
assert_raise(TypeError) { [1].zip(1) }
|
||||
end
|
||||
|
||||
assert 'Enumerator.produce' do
|
||||
assert_raise(ArgumentError) { Enumerator.produce }
|
||||
|
||||
# Without initial object
|
||||
passed_args = []
|
||||
enum = Enumerator.produce {|obj| passed_args << obj; (obj || 0).succ }
|
||||
assert_equal Enumerator, enum.class
|
||||
assert_take [1, 2, 3], enum
|
||||
assert_equal [nil, 1, 2], passed_args
|
||||
|
||||
# With initial object
|
||||
passed_args = []
|
||||
enum = Enumerator.produce(1) {|obj| passed_args << obj; obj.succ }
|
||||
assert_take [1, 2, 3], enum
|
||||
assert_equal [1, 2], passed_args
|
||||
|
||||
# Raising StopIteration
|
||||
words = %w[The quick brown fox jumps over the lazy dog]
|
||||
enum = Enumerator.produce { words.shift or raise StopIteration }
|
||||
assert_equal %w[The quick brown fox jumps over the lazy dog], enum.to_a
|
||||
|
||||
# Raising StopIteration
|
||||
object = [[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], "stuv", "wxyz"]
|
||||
enum = Enumerator.produce(object) {|obj|
|
||||
obj.respond_to?(:first) or raise StopIteration
|
||||
obj.first
|
||||
}
|
||||
assert_nothing_raised {
|
||||
assert_equal [
|
||||
[[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], "stuv", "wxyz"],
|
||||
[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"],
|
||||
[["abc", "def"], "ghi", "jkl"],
|
||||
["abc", "def"],
|
||||
"abc",
|
||||
], enum.to_a
|
||||
}
|
||||
end
|
||||
Reference in New Issue
Block a user