Created
January 20, 2020 15:49
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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