# = Coroutine
#
# Coroutines are program components like subroutines. Coroutines are more
# generic and flexible than subroutines, but are less widely used in
# practice. Coroutines were first introduced natively in Simula.
# Coroutines are well suited for implementing more familiar program
# components such as cooperative tasks, iterators, infinite lists, and pipes.
#
# This mixin solely depends on method read(n), which must be
# defined in the class/module where you mix in this module.
#
# == Usage
#
#   count = (ARGV.shift || 1000).to_i
#   input = (1..count).map { (rand * 10000).round.to_f / 100}
#
#   Producer = Coroutine.new do |me|
#     loop do
#       1.upto(6) do
#         me[:last_input] = input.shift
#         me.resume(Printer)
#       end
#       input.shift # discard every seventh input number
#     end
#   end
#   Printer = Coroutine.new do |me|
#     loop do
#       1.upto(8) do
#         me.resume(Producer)
#         if Producer[:last_input]
#           print Producer[:last_input], "\t"
#           Producer[:last_input] = nil
#         end
#         me.resume(Controller)
#       end
#       puts
#     end
#   end
#
#   Controller = Coroutine.new do |me|
#     until input.empty? do
#       me.resume(Printer)
#     end
#   end
#
#   Controller.run
#
# == Authors
#
# * Florian Frank
#
# == Copying
#
# Copyright (c) 2005 Florian Frank
#
# Ruby License
#
# This module is free software. You may use, modify, and/or redistribute this
# software under the same terms as Ruby.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.


# = Coroutine
#
# Coroutines are program components like subroutines. Coroutines are more
# generic and flexible than subroutines, but are less widely used in
# practice. Coroutines were first introduced natively in Simula.
# Coroutines are well suited for implementing more familiar program
# components such as cooperative tasks, iterators, infinite lists, and pipes.
#
# This mixin solely depends on method read(n), which must be
# defined in the class/module where you mix in this module.
#
# == Usage
#
#   count = (ARGV.shift || 1000).to_i
#   input = (1..count).map { (rand * 10000).round.to_f / 100}
#
#   Producer = Coroutine.new do |me|
#     loop do
#       1.upto(6) do
#         me[:last_input] = input.shift
#         me.resume(Printer)
#       end
#       input.shift # discard every seventh input number
#     end
#   end
#   Printer = Coroutine.new do |me|
#     loop do
#       1.upto(8) do
#         me.resume(Producer)
#         if Producer[:last_input]
#           print Producer[:last_input], "\t"
#           Producer[:last_input] = nil
#         end
#         me.resume(Controller)
#       end
#       puts
#     end
#   end
#
#   Controller = Coroutine.new do |me|
#     until input.empty? do
#       me.resume(Printer)
#     end
#   end
#
#   Controller.run
#
class Coroutine

  def initialize(data = {})
    @stopped = nil
    @data = data
    callcc do |@continue|
      return
    end
    yield self
    stop
  end

  attr_reader :stopped

  def run
    callcc do |@stopped|
      continue
    end
  end

  def stop
    @stopped.call
  end

  def resume(other)
    callcc do |@continue|
      other.continue(self)
    end
  end

  def continue(from = nil)
    @stopped = from.stopped if not @stopped and from
    @continue.call
  end

  def [](name)
    @data[name]
  end

  def []=(name, value)
    @data[name] = value
  end

  protected :stopped, :continue

end

