-
Notifications
You must be signed in to change notification settings - Fork 0
/
ziggen.zig
325 lines (292 loc) · 13.1 KB
/
ziggen.zig
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
const std = @import("std");
const assert = std.debug.assert;
const _debug = if (false) std.debug.print else _noopDebugPrint;
fn _noopDebugPrint(comptime fmt: []const u8, args: anytype) void {
_ = fmt;
_ = args;
// do nothing
}
var _nxt: usize = 0;
fn _debugGenNum() usize {
_nxt += 1;
return _nxt;
}
/// Returns an iterator (something with a `.next()` function) from the given generator.
/// A generator is something with a `.run(y: *Yielder(...))` function.
pub fn genIter(gen: anytype) GenIter(@TypeOf(gen), ValueTypeOfGenType(@TypeOf(gen))) {
return GenIter(@TypeOf(gen), ValueTypeOfGenType(@TypeOf(gen))){ ._gen = gen };
}
/// A type with a `yield()` function that is used to "return" values from a generator.
///
/// As an implementation detail, this is a tagged union value that
/// represents the current state of the generator iterator.
pub const Yielder = GenIterState;
fn ValueTypeOfGenType(comptime G: type) type {
const RunFunction = @TypeOf(G.run);
const run_function_args = @typeInfo(RunFunction).Fn.args;
std.debug.assert(run_function_args.len == 2); // .run() is expected to have two arguments (self: *@This(), y: *Yielder(...))
const Yielder_T_Pointer = run_function_args[1].arg_type.?;
const Yielder_T = @typeInfo(Yielder_T_Pointer).Pointer.child; // .run(...) takes a pointer (to a Yielder(...))
const GenIterState_T = Yielder_T;
const GenIterState_T_valued = TypeOfNamedFieldIn(@typeInfo(GenIterState_T).Union.fields, "_valued"); // .run(...) takes a *Yielder(...)
const Yielded_value = TypeOfNamedFieldIn(@typeInfo(GenIterState_T_valued).Struct.fields, "value");
const T = @typeInfo(Yielded_value).Optional.child;
return T;
}
fn TypeOfNamedFieldIn(comptime fields: anytype, field_name: []const u8) type {
for (fields) |f| {
if (std.mem.eql(u8, f.name, field_name)) {
return f.field_type;
}
} else @compileError("ziggen internal error: fields array doesn't contain expected field");
}
fn GenIter(comptime G: anytype, comptime T: type) type {
return struct {
_gen: G,
_state: GenIterState(T) = ._not_started,
_frame: @Frame(@This()._run_gen) = undefined,
/// This function is used to detect that `.run()` has returned
fn _run_gen(self: *@This()) void {
_debug("_run_gen(): enter\n", .{});
self._gen.run(&(self._state)); // the generator must have a .run(*Yielder(T)) function
_debug("_run_gen(): after run() in state {}\n", .{@as(_StateTag, self._state)});
self._state._suspend_with_value(null);
}
/// Return the next value of this generator iterator.
pub fn next(self: *@This()) ?T {
_debug("next(): enter state {}\n", .{@as(_StateTag, self._state)});
if (self._state == ._not_started) {
self._state = .{ ._running = null };
_debug("next(): to state {}\n", .{@as(_StateTag, self._state)});
const i = _debugGenNum();
_debug("> {}\n", .{i});
self._frame = async self._run_gen();
_debug("< {}\n", .{i});
}
while (true) {
switch (self._state) {
._not_started => unreachable,
._running => {
// still running after previous `async` or `resume`: suspended outside yield()
assert(self._state._running == null); // so we will not overwrite the frame pointer below
if (@hasDecl(G, "is_async") and G.is_async == true) {
self._state = .{ ._running = @frame() };
_debug("next(): to state {}\n", .{@as(_StateTag, self._state)});
_debug("next(): before suspend\n", .{});
suspend {} // ...so that this call of next() gets suspended, to be resumed by the client in yield(), or after, in _run_gen()
_debug("next(): after suspend in state {}\n", .{@as(_StateTag, self._state)});
assert(self._state == ._valued);
} else @panic("generator suspended but not in yield(); mark generator `const is_async = true;`?");
},
._valued => |value_state| {
if (value_state.value) |v| {
// .yield(v) has been called
self._state = .{ ._waiting = value_state.frame_pointer };
_debug("next(): to state {}\n", .{@as(_StateTag, self._state)});
return v;
} else {
// the generator function has returned, resume in _run_gen()
_debug("next(): before resume\n", .{});
const i = _debugGenNum();
_debug("> {}\n", .{i});
resume value_state.frame_pointer;
_debug("< {}\n", .{i});
_debug("next(): after resume\n", .{});
nosuspend await self._frame; // will never suspend anymore, so this next() call shouldn't either
self._state = ._stopped;
return null;
}
},
._waiting => |fp| {
self._state = .{ ._running = null };
_debug("next(): to state {}\n", .{@as(_StateTag, self._state)});
_debug("next(): before resume\n", .{});
const i = _debugGenNum();
_debug("> {}\n", .{i});
resume fp; // let the generator continue
_debug("< {}\n", .{i});
_debug("next(): after resume\n", .{});
assert(self._state == ._valued or self._state == ._running);
},
._stopped => {
return null;
},
}
}
}
};
}
const _StateTag = enum { _not_started, _running, _valued, _waiting, _stopped };
fn GenIterState(comptime T: type) type {
return union(_StateTag) {
/// the generator function was not yet called
_not_started: void,
/// the generator function did not reach yield() after being called or resumed (whichever came last),
/// but it optionally suspended in outside of yield(), as captured by next()
_running: ?anyframe,
/// the generator function has suspended in yield() or it has returned, and the value still has to be returned to the client
_valued: struct {
/// non-null if from yield(), null if the generator function returned
value: ?T,
/// the point where next() should resume
frame_pointer: anyframe,
},
/// the generator function has suspended in yield() or it has returned, and the value has already been returned
_waiting: anyframe,
/// the generator function has returned
_stopped: void,
/// Yield the given value from the generator that received this instance
pub fn yield(self: *@This(), value: T) void {
self._suspend_with_value(value);
}
fn _suspend_with_value(self: *@This(), value: ?T) void {
const orig_self: @This() = self.*;
assert(orig_self == ._running);
_debug("_suspend_with_value(): enter state {} (with value? {})\n", .{ @as(_StateTag, orig_self), value != null });
self.* = .{ ._valued = .{ .value = value, .frame_pointer = @frame() } };
_debug("_suspend_with_value(): to state {}\n", .{@as(_StateTag, self.*)});
_debug("_suspend_with_value(): before suspend\n", .{});
suspend {
if (orig_self._running) |fp| {
const i = _debugGenNum();
_debug("> {}\n", .{i});
resume fp;
// This point is only reached very late; when the event loop ends?
// For some reason, at that point our local state,
// the current stack frame, is not available anymore...
// So we should not be doing anything context-dependent here...
_debug("< ?\n", .{});
}
}
_debug("_suspend_with_value(): after suspend (elsewhere? {})\n", .{orig_self._running != null});
}
};
}
// TESTS AND EXAMPLES
// ------------------
const expectEqual = std.testing.expectEqual;
fn EmptySleeper(comptime asy: bool) type {
return struct {
pub const is_async = asy;
sleep_time_ms: ?usize = null,
pub fn run(self: *@This(), _: *Yielder(bool)) void {
if (self.sleep_time_ms) |ms| {
_debug("run(): before sleep\n", .{});
std.time.sleep(ms * std.time.ns_per_ms);
_debug("run(): after sleep\n", .{});
}
}
};
}
test "empty, sync" {
_debug("\nSTART\n", .{});
defer _debug("END\n", .{});
var iter = genIter(EmptySleeper(false){ .sleep_time_ms = null });
try expectEqual(@as(?bool, null), iter.next());
try expectEqual(@as(?bool, null), iter.next());
}
test "empty, async" {
// auto-skipped if not --test-evented-io, because EmptySleeper(true) is an async generator
assert(@import("root").io_mode == .evented);
_debug("\nSTART\n", .{});
defer _debug("END\n", .{});
var iter = genIter(EmptySleeper(true){ .sleep_time_ms = null });
try expectEqual(@as(?bool, null), iter.next());
try expectEqual(@as(?bool, null), iter.next());
}
test "empty sleeper, sync" {
if (@import("root").io_mode == .evented) {
// sleep() would suspend, making this non-async generator panic
return error.SkipZigTest;
}
// blocking I/O, therefore sleep() does not suspend
_debug("\nSTART\n", .{});
defer _debug("END\n", .{});
var iter = genIter(EmptySleeper(false){ .sleep_time_ms = 500 });
try expectEqual(@as(?bool, null), iter.next());
try expectEqual(@as(?bool, null), iter.next());
}
test "empty sleeper, async" {
// auto-skipped if not --test-evented-io, because EmptySleeper(true) is an async generator
assert(@import("root").io_mode == .evented);
_debug("\nSTART\n", .{});
defer _debug("END\n", .{});
var iter = genIter(EmptySleeper(true){ .sleep_time_ms = 500 });
try expectEqual(@as(?bool, null), iter.next());
try expectEqual(@as(?bool, null), iter.next());
}
const Bits = struct {
pub const is_async = true;
sleep_time_ms: ?usize = null,
pub fn run(self: *@This(), y: *Yielder(bool)) void {
if (self.sleep_time_ms) |ms| {
_debug("run(): before sleep\n", .{});
std.time.sleep(ms * std.time.ns_per_ms);
_debug("run(): after sleep\n", .{});
}
_debug("run(): before yield(false)\n", .{});
y.yield(false);
_debug("run(): after yield(false)\n", .{});
if (self.sleep_time_ms) |ms| {
_debug("run(): before sleep\n", .{});
std.time.sleep(ms * std.time.ns_per_ms);
_debug("run(): after sleep\n", .{});
}
_debug("run(): before yield(true)\n", .{});
y.yield(true);
_debug("run(): after yield(true)\n", .{});
if (self.sleep_time_ms) |ms| {
_debug("run(): before sleep\n", .{});
std.time.sleep(ms * std.time.ns_per_ms);
_debug("run(): after sleep\n", .{});
}
}
};
test "generate all bits, finite iterator" {
// auto-skipped if not --test-evented-io, because Bits is an async generator
assert(@import("root").io_mode == .evented);
_debug("\nSTART\n", .{});
defer _debug("END\n", .{});
var iter = genIter(Bits{ .sleep_time_ms = 500 });
_debug("client: before false\n", .{});
try expectEqual(@as(?bool, false), iter.next());
_debug("client: after false\n", .{});
_debug("client: before true\n", .{});
try expectEqual(@as(?bool, true), iter.next());
_debug("client: after true\n", .{});
try expectEqual(@as(?bool, null), iter.next());
try expectEqual(@as(?bool, null), iter.next());
}
const Nats = struct {
below: ?usize,
pub fn run(self: *@This(), y: *Yielder(usize)) void {
var i: usize = 0;
while (self.below == null or i < self.below.?) : (i += 1) {
y.yield(i);
}
}
};
test "sum the first 7 natural numbers" {
var iter = genIter(Nats{ .below = 7 });
var sum: usize = 0;
while (iter.next()) |i| {
sum += i;
}
try expectEqual(@as(usize, 21), sum);
}
test "generate all bits, bounded iterator" {
var iter = genIter(Nats{ .below = 2 });
try expectEqual(@as(?usize, 0), iter.next());
try expectEqual(@as(?usize, 1), iter.next());
try expectEqual(@as(?usize, null), iter.next());
try expectEqual(@as(?usize, null), iter.next());
}
test "sum by breaking infinite generator" {
var iter = genIter(Nats{ .below = null });
var sum: usize = 0;
while (iter.next()) |i| {
if (i == 7) break;
sum += i;
}
try expectEqual(@as(usize, 21), sum);
}