outer-iterator.rb

=begin
= OuterIterator
外部イテレータクラス。

== 例外
:OuterIterator::IterateFinishError
  繰り返しを既に終えているのに、次の要素取り出しを行った

== クラスメソッド
--- OuterIterator.new(obj=[])
--- OuterIterator.new(obj=[]){|obj_base| ... }
  objを操作する外部イテレータを生成する
  ブロックが与えられない場合、objがeachメソッドを持っていなければArgumentError
  ブロックが与えられると、それを評価した値をobjの代わりにイテレータを生成する
  (もちろん、評価された値がeachを持つオブジェクトでなければ例外を起こす)

== インスタンスメソッド
--- OuterIterator#more?
  繰り返し要素がまだ有れば真を返す

--- OuterIterator#next
  次の要素を取り出す
=end

class IteratorQueue
  def initialize(obj=[])
    unless obj.respond_to?(:each)
      raise(ArgumentError, "no iterator(#{obj.class}#each)")
    end
    @body = []
    obj.each do |item|
      @body.push item
    end
  end
  
  def enqueue;  @body.shift;  end
  
private
  def IteratorQueue.deligate_method(member_symbol, method_symbol)
    module_eval <<-EOS
      def #{method_symbol}
        @#{member_symbol}.#{method_symbol}
      end
    EOS
  end
  
  deligate_method(:body, :inspect)
  deligate_method(:body, :empty?)
end

################################################################################

class OuterIterator
  class IterateFinishError < RuntimeError
    def message
      'iterating already finished'
    end
  end
  
  private
  def inspect
    "#<#{self.class}:#{self.object_id}>"
  end
  
  def initialize(obj=[])
    obj = yield(obj) if block_given?
    begin
      @q = IteratorQueue.new(obj)
    rescue ArgumentError => ex
      raise(ArgumentError, ex.message)
    end
  end
  
  public
  def more?;  !@q.empty?;  end
  def next
    raise IterateFinishError if @q.empty?
    @q.enqueue
  end
end