diff options
| author | ksss <[email protected]> | 2014-03-13 21:51:12 +0900 |
|---|---|---|
| committer | ksss <[email protected]> | 2014-03-13 23:47:12 +0900 |
| commit | 463c5f83c3e5f379d4dd59deb17179915fbaf93b (patch) | |
| tree | 3685cb8f01670060796a525910bdc969b8c30dd1 | |
| parent | 91f2d47d341b96c7cf1803c332f1e276aee6a8e2 (diff) | |
| download | mruby-463c5f83c3e5f379d4dd59deb17179915fbaf93b.tar.gz mruby-463c5f83c3e5f379d4dd59deb17179915fbaf93b.zip | |
add mruby-enumerator
| -rw-r--r-- | mrbgems/default.gembox | 3 | ||||
| -rw-r--r-- | mrbgems/mruby-enumerator/mrbgem.rake | 4 | ||||
| -rw-r--r-- | mrbgems/mruby-enumerator/mrblib/core_mod.rb | 98 | ||||
| -rw-r--r-- | mrbgems/mruby-enumerator/mrblib/enumerator.rb | 621 | ||||
| -rw-r--r-- | mrbgems/mruby-enumerator/test/enumerator.rb | 398 |
5 files changed, 1124 insertions, 0 deletions
diff --git a/mrbgems/default.gembox b/mrbgems/default.gembox index 33ee99be0..2f436e5b6 100644 --- a/mrbgems/default.gembox +++ b/mrbgems/default.gembox @@ -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" diff --git a/mrbgems/mruby-enumerator/mrbgem.rake b/mrbgems/mruby-enumerator/mrbgem.rake new file mode 100644 index 000000000..c2e6a4a39 --- /dev/null +++ b/mrbgems/mruby-enumerator/mrbgem.rake @@ -0,0 +1,4 @@ +MRuby::Gem::Specification.new('mruby-enumerator') do |spec| + spec.license = 'MIT' + spec.author = 'mruby developers' +end diff --git a/mrbgems/mruby-enumerator/mrblib/core_mod.rb b/mrbgems/mruby-enumerator/mrblib/core_mod.rb new file mode 100644 index 000000000..968cc6ac4 --- /dev/null +++ b/mrbgems/mruby-enumerator/mrblib/core_mod.rb @@ -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 diff --git a/mrbgems/mruby-enumerator/mrblib/enumerator.rb b/mrbgems/mruby-enumerator/mrblib/enumerator.rb new file mode 100644 index 000000000..4f44a2d21 --- /dev/null +++ b/mrbgems/mruby-enumerator/mrblib/enumerator.rb @@ -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 || [email protected]? + @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 diff --git a/mrbgems/mruby-enumerator/test/enumerator.rb b/mrbgems/mruby-enumerator/test/enumerator.rb new file mode 100644 index 000000000..c790c1367 --- /dev/null +++ b/mrbgems/mruby-enumerator/test/enumerator.rb @@ -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 |
