mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
add mruby-enumerator
This commit is contained in:
@@ -50,6 +50,9 @@ MRuby::GemBox.new do |conf|
|
||||
# Use Fiber class
|
||||
conf.gem :core => "mruby-fiber"
|
||||
|
||||
# Use Enumerator class (require mruby-fiber)
|
||||
conf.gem :core => "mruby-enumerator"
|
||||
|
||||
# Use extended toplevel object (main) methods
|
||||
conf.gem :core => "mruby-toplevel-ext"
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
MRuby::Gem::Specification.new('mruby-enumerator') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
end
|
||||
@@ -0,0 +1,98 @@
|
||||
##
|
||||
# modifying existing methods
|
||||
##
|
||||
|
||||
# See /mrblib/kernel.rb
|
||||
module Kernel
|
||||
def loop
|
||||
return to_enum :loop unless block_given?
|
||||
|
||||
while(true)
|
||||
yield
|
||||
end
|
||||
rescue => StopIteration
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# See /mrblib/numeric.rb
|
||||
module Integral
|
||||
def times &block
|
||||
return to_enum :times unless block_given?
|
||||
|
||||
i = 0
|
||||
while i < self
|
||||
block.call i
|
||||
i += 1
|
||||
end
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# See /mrblib/enum.rb
|
||||
module Enumerable
|
||||
def collect(&block)
|
||||
return to_enum :collect unless block_given?
|
||||
|
||||
ary = []
|
||||
self.each{|val|
|
||||
ary.push(block.call(val))
|
||||
}
|
||||
ary
|
||||
end
|
||||
alias map collect
|
||||
end
|
||||
|
||||
# See /mrblib/array.rb
|
||||
class Array
|
||||
def each(&block)
|
||||
return to_enum :each unless block_given?
|
||||
|
||||
idx, length = -1, self.length-1
|
||||
while idx < length and length <= self.length and length = self.length-1
|
||||
elm = self[idx += 1]
|
||||
unless elm
|
||||
if elm == nil and length >= self.length
|
||||
break
|
||||
end
|
||||
end
|
||||
block.call(elm)
|
||||
end
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# See /mrblib/hash.rb
|
||||
class Hash
|
||||
def each(&block)
|
||||
return to_enum :each unless block_given?
|
||||
|
||||
self.keys.each { |k| block.call [k, self[k]] }
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# See /mrblib/range.rb
|
||||
class Range
|
||||
def each &block
|
||||
return to_enum :each unless block_given?
|
||||
|
||||
val = self.first
|
||||
unless val.respond_to? :succ
|
||||
raise TypeError, "can't iterate"
|
||||
end
|
||||
|
||||
last = self.last
|
||||
return self if (val <=> last) > 0
|
||||
|
||||
while((val <=> last) < 0)
|
||||
block.call(val)
|
||||
val = val.succ
|
||||
end
|
||||
|
||||
if not exclude_end? and (val <=> last) == 0
|
||||
block.call(val)
|
||||
end
|
||||
self
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,621 @@
|
||||
##
|
||||
# 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.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
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# Enumerator.new(size = nil) { |yielder| ... }
|
||||
# Enumerator.new(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]
|
||||
#
|
||||
def initialize obj=nil, meth=:each, *args, &block
|
||||
if block_given?
|
||||
obj = Generator.new &block
|
||||
else
|
||||
raise ArgumentError unless obj
|
||||
end
|
||||
|
||||
@obj = obj
|
||||
@meth = meth
|
||||
@args = args.dup
|
||||
@fib = nil
|
||||
@dst = nil
|
||||
@lookahead = nil
|
||||
@feedvalue = nil
|
||||
@stop_exc = false
|
||||
end
|
||||
attr_accessor :obj, :meth, :args, :fib
|
||||
private :obj, :meth, :args, :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
|
||||
return to_enum :with_index, offset unless block_given?
|
||||
raise TypeError, "no implicit conversion of #{offset.class} into Integer" unless offset.respond_to?(:to_int)
|
||||
|
||||
n = offset.to_int
|
||||
each do |i|
|
||||
yield [i,n]
|
||||
n += 1
|
||||
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
|
||||
with_index
|
||||
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
|
||||
return to_enum :with_object, offset unless block_given?
|
||||
|
||||
each do |i|
|
||||
yield [i,object]
|
||||
end
|
||||
object
|
||||
end
|
||||
|
||||
def inspect
|
||||
return "#<#{self.class}: uninitialized>" unless @obj
|
||||
"#<#{self.class}: #{@obj}:#{@meth}>"
|
||||
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
|
||||
#
|
||||
# "Hello, world!".scan(/\w+/) #=> ["Hello", "world"]
|
||||
# "Hello, world!".to_enum(:scan, /\w+/).to_a #=> ["Hello", "world"]
|
||||
# "Hello, world!".to_enum(:scan).each(/\w+/).to_a #=> ["Hello", "world"]
|
||||
#
|
||||
# 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
|
||||
if 0 < argv.length
|
||||
obj = self.dup
|
||||
args = obj.args
|
||||
if !args.empty?
|
||||
args = args.dup
|
||||
args.concat argv
|
||||
else
|
||||
args = argv.dup
|
||||
end
|
||||
@args = args
|
||||
end
|
||||
return self unless block_given?
|
||||
@obj.__send__ @meth, *@args, &block
|
||||
end
|
||||
|
||||
##
|
||||
# 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
|
||||
ary2sv next_values, false
|
||||
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
|
||||
|
||||
raise NotImplementedError, "Enumerator require Fiber" unless Object.const_defined?(:Fiber)
|
||||
|
||||
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
|
||||
ary2sv peek_values, true
|
||||
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
|
||||
def ary2sv args, dup
|
||||
return args unless args.kind_of? Array
|
||||
|
||||
case args.length
|
||||
when 0
|
||||
nil
|
||||
when 1
|
||||
args[0]
|
||||
else
|
||||
return args.dup if dup
|
||||
args
|
||||
end
|
||||
end
|
||||
private :ary2sv
|
||||
|
||||
# just for internal
|
||||
class Generator
|
||||
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_given?
|
||||
|
||||
@proc = block
|
||||
end
|
||||
|
||||
def yield *args
|
||||
@proc.call *args
|
||||
end
|
||||
|
||||
def << *args
|
||||
self.yield *args
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class StopIteration < IndexError
|
||||
attr_accessor :result
|
||||
end
|
||||
|
||||
module Kernel
|
||||
##
|
||||
# call-seq:
|
||||
# obj.to_enum(method = :each, *args) -> enum
|
||||
# obj.enum_for(method = :each, *args) -> enum
|
||||
# obj.to_enum(method = :each, *args) {|*args| block} -> enum
|
||||
# obj.enum_for(method = :each, *args){|*args| block} -> enum
|
||||
#
|
||||
# Creates a new Enumerator which will enumerate by calling +method+ on
|
||||
# +obj+, passing +args+ if any.
|
||||
#
|
||||
# If a block is given, it will be used to calculate the size of
|
||||
# the enumerator without the need to iterate it (see Enumerator#size).
|
||||
#
|
||||
# === 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 and a sizing block:
|
||||
#
|
||||
# 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) do # __method__ is :repeat here
|
||||
# sz = size # Call size and multiply by n...
|
||||
# sz * n if sz # but return nil if size itself is nil
|
||||
# end
|
||||
# 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
|
||||
@@ -0,0 +1,398 @@
|
||||
@obj = Object.new
|
||||
class << @obj
|
||||
include Enumerable
|
||||
def foo *a
|
||||
a.each { |x| yield x }
|
||||
end
|
||||
end
|
||||
|
||||
assert 'Enumerator' do
|
||||
assert_equal Class, Enumerator.class
|
||||
end
|
||||
|
||||
assert 'Enumerator' 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_equal [1,2,3], Enumerator.new(@obj, :foo, 1,2,3).to_a
|
||||
assert_equal [1,2,3], Enumerator.new { |y| i = 0; loop { y << (i += 1) } }.take(3)
|
||||
assert_raise(ArgumentError) { Enumerator.new }
|
||||
enum = @obj.to_enum
|
||||
assert_raise(NoMethodError) { enum.each {} }
|
||||
|
||||
# examples
|
||||
fib = Enumerator.new do |y|
|
||||
a = b = 1
|
||||
loop do
|
||||
y << a
|
||||
a, b = b, a + b
|
||||
end
|
||||
end
|
||||
assert_equal fib.take(10), [1,1,2,3,5,8,13,21,34,55]
|
||||
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)
|
||||
end
|
||||
|
||||
assert 'Enumerator#with_index nonnum offset' do
|
||||
s = Object.new
|
||||
def s.to_int; 1 end
|
||||
assert_equal([[1,1],[2,2],[3,3]], @obj.to_enum(:foo, 1, 2, 3).with_index(s).to_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#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#inspect' do
|
||||
e = (0..10).each
|
||||
assert_equal("#<Enumerator: 0..10:each>", 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#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
|
||||
assert_equal Enumerator, [].to_enum.class
|
||||
assert_raise(ArgumentError){ nil.to_enum }
|
||||
end
|
||||
|
||||
|
||||
assert 'modifying existing methods' do
|
||||
e = 3.times
|
||||
i = 0
|
||||
loop_ret = loop {
|
||||
assert_equal i, e.next
|
||||
i += 1
|
||||
}
|
||||
assert_nil loop_ret
|
||||
|
||||
assert_equal Enumerator, loop.class
|
||||
assert_equal Enumerator, 3.times.class
|
||||
assert_equal Enumerator, [].each.class
|
||||
assert_equal Enumerator, [].map.class
|
||||
assert_equal Enumerator, {a:1}.each.class
|
||||
assert_equal Enumerator, (1..5).each.class
|
||||
end
|
||||
Reference in New Issue
Block a user