Skip to content

Instantly share code, notes, and snippets.

@havenwood
Created January 20, 2020 15:49
Show Gist options
  • Save havenwood/964aaba914ace2d561d85b4110ead0ce to your computer and use it in GitHub Desktop.
Save havenwood/964aaba914ace2d561d85b4110ead0ce to your computer and use it in GitHub Desktop.
My WIP implementation of Enumerator backed by a Fiber (like the actually are) but in pure Ruby
# frozen_string_literal: true
require 'delegate'
class Fiberator < DelegateClass Fiber
class Rewind < StandardError
end
NOTHING = Object.new
private_constant :NOTHING
STOP_ITERATION = Object.new
private_constant :STOP_ITERATION
include Enumerable
def self.produce(value = NOTHING, &block)
raise ArgumentError unless block_given?
new value, &block
end
def initialize(value = NOTHING)
fiber = Fiber.new do
initial_value = value
begin
value = initial_value
if value == NOTHING
value = nil
else
Fiber.yield value
end
loop do
value = yield value
Fiber.yield value
rescue StopIteration
break Fiber.yield STOP_ITERATION
end
rescue Rewind
Fiber.yield self
retry
end
end
super fiber
end
def each
loop do
value = resume
break if value == STOP_ITERATION
yield value
end
end
alias next resume
def rewind
raise Rewind
end
def size
Float::INFINITY
end
end
require 'test/unit'
class TestFiberator < Test::Unit::TestCase
def test_next
e = Fiberator.produce(0, &:next)
3.times do |i|
assert_equal i, e.next
end
end
def test_rewind
e = Fiberator.produce(0, &:next)
assert_equal(0, e.next)
assert_equal(Fiberator, e.rewind.class)
assert_equal(0, e.next)
end
def test_enums_rewind
e = Fiberator.produce(0, &:next)
assert_equal([0, 1, 2], e.first(3))
assert_equal([0, 1, 2], e.first(3))
end
def test_produce_without_block
assert_raise(ArgumentError) { Fiberator.produce }
end
def test_produce_without_initial_object
passed_args = []
enum = Fiberator.produce do |obj|
passed_args << obj
(obj || 0).succ
end
assert_instance_of(Fiberator, enum)
assert_equal Float::INFINITY, enum.size
assert_equal [1, 2, 3], enum.take(3)
assert_equal [nil, 1, 2], passed_args
end
def test_produce_with_initial_object
passed_args = []
enum = Fiberator.produce(1) do |obj|
passed_args << obj
obj.succ
end
assert_instance_of(Fiberator, enum)
assert_equal Float::INFINITY, enum.size
assert_equal [1, 2, 3], enum.take(3)
assert_equal [1, 2], passed_args
end
def test_produce_with_initial_keyword_arguments
passed_args = []
enum = Fiberator.produce(a: 1, b: 1) do |obj|
passed_args << obj
obj.shift if obj.respond_to?(:shift)
end
assert_instance_of(Fiberator, enum)
assert_equal Float::INFINITY, enum.size
assert_equal [{b: 1}, [1], :a, nil], enum.take(4)
assert_equal [{b: 1}, [1], :a], passed_args
end
def test_produce_raising_stop_iteration
words = 'The quick brown fox jumps over the lazy dog.'.scan(/\w+/)
enum = Fiberator.produce { words.shift || raise(StopIteration) }
assert_equal Float::INFINITY, enum.size
assert_instance_of(Fiberator, enum)
assert_equal %w[The quick brown fox jumps over the lazy dog], enum.to_a
end
def test_produce_stop_iteration
object = [[[%w[abc def], 'ghi', 'jkl'], 'mno', 'pqr'], 'stuv', 'wxyz']
enum = Fiberator.produce(object) do |obj|
obj.respond_to?(:first) || raise(StopIteration)
obj.first
end
assert_equal Float::INFINITY, enum.size
assert_instance_of(Fiberator, enum)
assert_nothing_raised do
assert_equal [
[[[%w[abc def], 'ghi', 'jkl'], 'mno', 'pqr'], 'stuv', 'wxyz'],
[[%w[abc def], 'ghi', 'jkl'], 'mno', 'pqr'],
[%w[abc def], 'ghi', 'jkl'],
%w[abc def],
'abc'
], enum.to_a
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment