-
Notifications
You must be signed in to change notification settings - Fork 22
/
enumerator.fy
189 lines (156 loc) · 4.28 KB
/
enumerator.fy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
class Fancy {
class Enumerator {
class Generator {
def initialize: @block
def each: block {
@block call: [block]
}
}
def initialize: @collection {
"""
@collection Collection to iterate over.
Initializes a new Enumerator with a given @collection,
using #each: for iteration.
"""
@iterator = 'each:
rewind
}
def initialize: @collection with: @iterator {
"""
@collection Collection to iterate over.
@iterator Selector to use to iterate over @collection.
Initializes a new Enumerator with a given @collection
and @iterator selector to be used for iteration.
"""
rewind
}
def next {
"""
@return Next element in the collection this enumerator is attached to.
Returns the next element in the collection this enumerator is attached to.
It will move the internal position forward (compared to e.g. #peek, which doesn't).
Example:
a = [1,2,3]
e = a to_enum
e next # => 1
e next # => 2
e next # => 3
e next # => raises Fancy StopIteration
"""
if: @peeked then: {
@peeked = false
@peek
} else: {
result = @fiber resume
if: (@fiber alive?) then: {
return result
} else: {
(Fancy StopIteration new: result) raise!
}
}
}
def ended? {
"""
@return @true if the enumerator has ended (no more values left), @false otherwise.
Indicates if an enumerator has ended (no more values left).
"""
@fiber alive? not
}
def peek {
"""
Returns the next object in the Enumerator, but doesn't move the
internal position forward.
When the position reaches the end, a Fancy StopIteration exception is
raised.
Example:
a = [1,2,3]
e = a to_enum
e next p #=> 1
e peek p #=> 2
e peek p #=> 2
e peek p #=> 2
e next p #=> 2
e next p #=> 3
e next p #=> raises Fancy StopIteration
"""
unless: @peeked do: {
@peeked = true
@peek = @fiber resume
if: (@fiber alive?) then: {
return @peek
} else: {
(Fancy StopIteration new: result) raise!
}
}
return @peek
}
def rewind {
"""
Resets the enumerator to start from the collection's beginning.
"""
@peeked = false
@peek = nil
@fiber = Fiber new: {
param = |element| { yield: element }
@collection receive_message: @iterator with_params: [param]
}
}
def with: object each: block {
"""
@object Object to pass along to @block with each element in the collection.
@block A @Block@ to be called with each element in the collection and @object.
Similar to #each: but also passing in a given @object to each invocation of @block.
"""
each: |element| {
block call: [element, object]
}
return object
}
def each: block {
"""
@block @Block@ to be called with each element in the collection (iteration).
Calls a given @Block@ with each element in the collection this enumerator is attached to.
Used for iterating over the collection using this enumerator.
"""
loop: {
try {
block call: [next]
} catch Fancy StopIteration {
return self
}
}
}
include: Fancy Enumerable
def chunk: block {
Generator new: |inner_block| {
enums = []
last = nil
previous = nil
stack = []
each: |element| {
result = (block call: [element]) not not
if: (previous == result) then: {
stack << element
} else: {
previous if_nil: {
# wait one gap to call
} else: {
inner_block call: [[previous, stack]]
}
previous = result
stack = [element]
last = [result, stack]
enums << last
}
}
self
} . to_enum
}
def to_a {
output = []
each: |element| { output << element }
rewind
output
}
}
}