├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── build.zig.zon ├── build └── docs.sh ├── src ├── enumerable.zig └── meta.zig └── zls.json /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache 2 | zig-out 3 | .zig-cache 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Laurynas Lazauskas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zig Enumerable ⚡ 2 | 3 | Functional vibes for data processing as sequences. 4 | 5 | ```zig 6 | const std = @import("std"); 7 | const enumerable = @import("enumerable"); 8 | 9 | test "example" { 10 | try expectEqualIter( 11 | "(1,2,3)", 12 | enumerable.from(std.mem.tokenizeAny(u8, "foo=1;bar=2;baz=3", "=;").buffer) 13 | .where(std.ascii.isDigit) 14 | .intersperse(',') 15 | .prepend('(') 16 | .append(')'), 17 | ); 18 | } 19 | ``` 20 | 21 | ## 📦 Get started 22 | 23 | ```bash 24 | zig fetch --save https://github.com/lawrence-laz/zig-enumerable/archive/master.tar.gz 25 | ``` 26 | 27 | ```zig 28 | // build.zig 29 | const enumerable = b.dependency("enumerable", .{ 30 | .target = target, 31 | .optimize = optimize, 32 | }); 33 | 34 | exe.root_module.addImport("enumerable", enumerable.module("enumerable")); 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) void { 4 | const target = b.standardTargetOptions(.{}); 5 | 6 | const optimize = b.standardOptimizeOption(.{}); 7 | 8 | _ = b.addModule("enumerable", .{ 9 | .root_source_file = b.path("src/enumerable.zig"), 10 | }); 11 | 12 | const lib = b.addStaticLibrary(.{ 13 | .name = "enumerable", 14 | .root_source_file = b.path("src/enumerable.zig"), 15 | .target = target, 16 | .optimize = optimize, 17 | }); 18 | 19 | b.installArtifact(lib); 20 | 21 | const main_tests = b.addTest(.{ 22 | .root_source_file = b.path("src/enumerable.zig"), 23 | .target = target, 24 | .optimize = optimize, 25 | }); 26 | 27 | const run_main_tests = b.addRunArtifact(main_tests); 28 | 29 | const test_step = b.step("test", "Run library tests"); 30 | test_step.dependOn(&run_main_tests.step); 31 | 32 | const install_docs = b.addInstallDirectory(.{ 33 | .source_dir = lib.getEmittedDocs(), 34 | .install_dir = .prefix, 35 | .install_subdir = "docs", 36 | }); 37 | 38 | const docs_step = b.step("docs", "Build and install documentation"); 39 | docs_step.dependOn(&install_docs.step); 40 | } 41 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = "enumerable", 3 | .version = "0.1.1", 4 | .minimum_zig_version = "0.13.0", 5 | .paths = .{ 6 | "build.zig", 7 | "build.zig.zon", 8 | "src", 9 | "LICENSE", 10 | "README.md", 11 | "build", 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /build/docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function yes_or_no { 4 | while true; do 5 | read -p "$* [y/n]: " yn 6 | case $yn in 7 | [Yy]*) return 0 ;; 8 | [Nn]*) return 1 ;; 9 | esac 10 | done 11 | } 12 | 13 | rm -rf zig-cache 14 | rm -rf zig-out 15 | zig build docs 16 | 17 | yes_or_no "Start local HTTP server hosting the documentation?" \ 18 | && http-server ./zig-out/docs/ -p 8000 \ 19 | 20 | -------------------------------------------------------------------------------- /src/enumerable.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const meta = @import("meta.zig"); 3 | const expectEqual = @import("std").testing.expectEqual; 4 | 5 | const enumerable = @This(); 6 | 7 | /// Returns the type of an iterator over a type `T`. 8 | pub fn Iterator(comptime T: type) type { 9 | return switch (@typeInfo(T)) { 10 | .Pointer => |type_info| switch (type_info.size) { 11 | .One => switch (@typeInfo(type_info.child)) { 12 | .Array => SliceIterator(std.meta.Elem(T)), 13 | else => type_info.child, 14 | }, 15 | .Slice => if (type_info.is_const) SliceIterator(std.meta.Elem(T)) else MutSliceIterator(std.meta.Elem(T)), 16 | else => T, 17 | }, 18 | else => T, 19 | }; 20 | } 21 | 22 | /// Returns the type of an item from iterator of type `TIter`. 23 | pub fn IteratorItem(comptime TIter: type) type { 24 | return @typeInfo(@typeInfo(@TypeOf(Iterator(TIter).next)).Fn.return_type.?).Optional.child; 25 | } 26 | 27 | /// Returns a new iterator for the given `iterable`. 28 | /// 29 | /// The function determines the type of the `iterable` and adapts the iterator creation accordingly. 30 | /// It supports pointers, slices, and other types directly. 31 | pub inline fn from(iterable: anytype) Iterator(@TypeOf(iterable)) { 32 | return switch (@typeInfo(@TypeOf(iterable))) { 33 | .Pointer => |info| switch (info.size) { 34 | .One => switch (@typeInfo(info.child)) { 35 | .Array => fromSlice(iterable), 36 | else => iterable.*, 37 | }, 38 | .Slice => if (info.is_const) fromSlice(iterable) else fromMutSlice(iterable), 39 | else => iterable, 40 | }, 41 | else => iterable, 42 | }; 43 | } 44 | 45 | test from { 46 | var foo = from(std.mem.tokenizeSequence(u8, "foo\nbar\nbaz\n", "\n")); 47 | try std.testing.expectEqualStrings("foo", foo.next().?); 48 | try std.testing.expectEqualStrings("bar", foo.next().?); 49 | try std.testing.expectEqualStrings("baz", foo.next().?); 50 | try std.testing.expectEqual(@as(?[]const u8, null), foo.next()); 51 | 52 | try expectEqualIter("abcd", from("abcd")); 53 | try expectEqualIter(&[_]i32{ -1, 0, 1 }, from(&[_]i32{ -1, 0, 1 })); 54 | } 55 | 56 | /// Returns an iterator for the provided slice. 57 | inline fn fromSlice(slice: anytype) SliceIterator(std.meta.Elem(@TypeOf(slice))) { 58 | return .{ .slice = slice }; 59 | } 60 | 61 | /// A generic iterator for slices. 62 | /// 63 | /// This iterator supports both forward and reverse iteration. 64 | pub fn SliceIterator(comptime TItem: type) type { 65 | return struct { 66 | const Self = @This(); 67 | 68 | slice: []const TItem, 69 | index: usize = 0, 70 | reversed: bool = false, 71 | completed: bool = false, 72 | 73 | /// Advances the iterator and returns the next item. 74 | /// 75 | /// Returns `null` when the sequence is exhausted. 76 | pub fn next(self: *Self) ?TItem { 77 | if (self.reversed) { 78 | if (self.index >= 0 and !self.completed) { 79 | const item = self.slice[self.index]; 80 | if (self.index > 0) { 81 | self.index -= 1; 82 | } else { 83 | self.completed = true; 84 | } 85 | return item; 86 | } 87 | } else { 88 | if (self.index < self.slice.len) { 89 | const item = self.slice[self.index]; 90 | self.index += 1; 91 | return item; 92 | } 93 | } 94 | 95 | return null; 96 | } 97 | 98 | /// Returns a new iterator that iterates over the slice in reverse order. 99 | pub fn reverse(self: *const Self) SliceIterator(TItem) { 100 | return .{ 101 | .reversed = true, 102 | .index = self.slice.len - 1, 103 | .slice = self.slice, 104 | }; 105 | } 106 | 107 | test reverse { 108 | try expectEqualIter("fedcba", from("abcdef").reverse()); 109 | } 110 | 111 | pub usingnamespace enumerable; 112 | pub usingnamespace enumerable_mutable_and_indexable; 113 | }; 114 | } 115 | 116 | /// Returns a mutable iterator for the provided slice. 117 | inline fn fromMutSlice(slice: anytype) MutSliceIterator(std.meta.Elem(@TypeOf(slice))) { 118 | return .{ .slice = slice }; 119 | } 120 | 121 | /// A generic mutable iterator for slices. 122 | pub fn MutSliceIterator(comptime TItem: type) type { 123 | return struct { 124 | const Self = @This(); 125 | 126 | slice: []TItem, 127 | index: usize = 0, 128 | reversed: bool = false, 129 | completed: bool = false, 130 | 131 | /// Advances the iterator and returns the next item. 132 | /// 133 | /// Returns `null` when the sequence is exhausted. 134 | pub fn next(self: *Self) ?TItem { 135 | if (self.reversed) { 136 | if (self.index >= 0 and !self.completed) { 137 | const item = self.slice[self.index]; 138 | if (self.index > 0) { 139 | self.index -= 1; 140 | } else { 141 | self.completed = true; 142 | } 143 | return item; 144 | } 145 | } else { 146 | if (self.index < self.slice.len) { 147 | const item = self.slice[self.index]; 148 | self.index += 1; 149 | return item; 150 | } 151 | } 152 | 153 | return null; 154 | } 155 | 156 | pub usingnamespace enumerable; 157 | pub usingnamespace enumerable_mutable_and_indexable; 158 | }; 159 | } 160 | 161 | /// Generates a sequence of numbers from `from_inclusive` to `to_exclusive` with a step size of 1. 162 | pub inline fn sequence( 163 | /// The type of the numbers in the sequence. 164 | comptime TNumber: type, 165 | /// The value of the first number in the sequence (inclusive). 166 | from_inclusive: TNumber, 167 | /// The value of the upper bound of the sequence (exclusive). 168 | to_exclusive: TNumber, 169 | ) SequenceIterator(TNumber) { 170 | return .{ 171 | .from_inclusive = from_inclusive, 172 | .to_exclusive = to_exclusive, 173 | .current = from_inclusive, 174 | }; 175 | } 176 | 177 | test sequence { 178 | try expectEqualIter(&[_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, sequence(u8, 0, 10)); 179 | try expectEqualIter(&[_]u8{ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }, sequence(u8, 10, 0)); 180 | try expectEqualIter(&[_]u8{}, sequence(u8, 0, 0)); 181 | } 182 | 183 | /// An iterator for generating sequences of numbers with a step size of 1. 184 | /// 185 | /// TODO: Implement reverse 186 | pub fn SequenceIterator(comptime TNumber: type) type { 187 | return struct { 188 | const Self = @This(); 189 | 190 | from_inclusive: TNumber, 191 | to_exclusive: TNumber, 192 | current: TNumber, 193 | positive_step: ?bool = null, 194 | 195 | pub fn next(self: *Self) ?TNumber { 196 | if (self.positive_step == null) { 197 | self.positive_step = self.to_exclusive > self.from_inclusive; 198 | } 199 | if ((self.positive_step.? and self.current < self.to_exclusive) or 200 | (!self.positive_step.? and self.current > self.to_exclusive)) 201 | { 202 | const current = self.current; 203 | if (self.positive_step.?) { 204 | self.current += 1; 205 | } else { 206 | self.current -= 1; 207 | } 208 | return current; 209 | } 210 | 211 | return null; 212 | } 213 | 214 | pub usingnamespace enumerable; 215 | }; 216 | } 217 | 218 | /// Generates a sequence of numbers from `from_inclusive` to `to_exclusive` with a specified step size. 219 | pub inline fn sequenceEvery( 220 | /// The type of the numbers in the sequence. 221 | comptime TNumber: type, 222 | /// The value of the first number in the sequence (inclusive). 223 | from_inclusive: TNumber, 224 | /// The value of the upper bound of the sequence (exclusive). 225 | to_exclusive: TNumber, 226 | /// The size of the step between each element in the sequence. 227 | step: TNumber, 228 | ) SequenceEveryIterator(TNumber) { 229 | return .{ 230 | .from_inclusive = from_inclusive, 231 | .to_exclusive = to_exclusive, 232 | .current = from_inclusive, 233 | .step = step, 234 | }; 235 | } 236 | 237 | test sequenceEvery { 238 | try expectEqualIter(&[_]u8{ 0, 2, 4, 6, 8 }, sequenceEvery(u8, 0, 10, 2)); 239 | } 240 | 241 | /// An iterator for generating sequences of numbers with a specified step size. 242 | /// 243 | /// This iterator generates a sequence from `from_inclusive` to `to_exclusive` with a step size of `step`. 244 | pub fn SequenceEveryIterator(comptime TNumber: type) type { 245 | return struct { 246 | const Self = @This(); 247 | 248 | from_inclusive: TNumber, 249 | to_exclusive: TNumber, 250 | step: TNumber, 251 | current: TNumber, 252 | 253 | /// Advances the iterator and returns the next number in the sequence. 254 | /// 255 | /// Returns `null` when the sequence is exhausted. 256 | pub fn next(self: *Self) ?TNumber { 257 | if (self.current < self.to_exclusive) { 258 | const current = self.current; 259 | self.current += self.step; 260 | return current; 261 | } 262 | 263 | return null; 264 | } 265 | 266 | pub usingnamespace enumerable; 267 | }; 268 | } 269 | 270 | /// Generates a countdown sequence starting from the given `starting_number`. 271 | pub fn countdown(starting_number: anytype) CountdownIterator(@TypeOf(starting_number)) { 272 | return .{ .current = starting_number - 1 }; 273 | } 274 | 275 | test countdown { 276 | try expectEqualIter(&[_]u8{ 2, 1, 0 }, countdown(@as(u8, 3))); 277 | } 278 | 279 | // TODO: Need a better name here 280 | // Or actually need the original function to be typed, but I also need a function with single param? 281 | pub fn countdown2(comptime T: type) fn (T) CountdownIterator(T) { 282 | return struct { 283 | fn f(starting_number: T) CountdownIterator(T) { 284 | return .{ .current = starting_number - 1 }; 285 | } 286 | }.f; 287 | } 288 | 289 | /// An iterator for generating countdown sequences. 290 | /// 291 | /// This iterator generates a sequence starting from a given numeric value, decrementing by 1 until reaching 0. 292 | pub fn CountdownIterator(comptime TItem: type) type { 293 | return struct { 294 | const Self = @This(); 295 | 296 | current: ?TItem, 297 | 298 | /// Advances the iterator and returns the next value in the countdown sequence. 299 | /// 300 | /// Returns `null` when the sequence is exhausted. 301 | pub fn next(self: *Self) ?TItem { 302 | if (self.current) |current_value| { 303 | if (current_value > 0) { 304 | self.current.? -= 1; 305 | } else { 306 | self.current = null; 307 | } 308 | return current_value; 309 | } else { 310 | return null; 311 | } 312 | } 313 | 314 | pub usingnamespace enumerable; 315 | }; 316 | } 317 | 318 | /// Counts the number of items in the sequence. 319 | pub inline fn count(self: anytype) usize { 320 | var self_copy = self.*; 321 | var result: usize = 0; 322 | while (self_copy.next()) |_| { 323 | result += 1; 324 | } 325 | return result; 326 | } 327 | 328 | test count { 329 | try expectEqual(@as(usize, 0), from("").count()); 330 | try expectEqual(@as(usize, 3), from("abc").count()); 331 | } 332 | 333 | /// Returns the sum of all items in the sequence. 334 | /// 335 | /// Items in the sequence are expected to support the `+=` operator. 336 | pub inline fn sum(self: anytype) IteratorItem(@TypeOf(self)) { 337 | var self_copy = self.*; 338 | var result: IteratorItem(@TypeOf(self)) = 0; 339 | while (self_copy.next()) |number| { 340 | result += number; 341 | } 342 | return result; 343 | } 344 | 345 | test sum { 346 | try expectEqual(@as(u8, 0), from(&[_]u8{}).sum()); 347 | try expectEqual(@as(u8, 6), from(&[_]u8{ 1, 2, 3 }).sum()); 348 | try expectEqual(@as(i32, -10), from(&[_]i32{ -30, 5, 15 }).sum()); 349 | try expectEqual(@as(f32, -0.9), from(&[_]f32{ -1.5, 0.2, 0.4 }).sum()); 350 | } 351 | 352 | /// Checks if the sequence contains at least one item satisfying the provided condition. 353 | /// 354 | /// An empty sequence always returns `false`. 355 | pub inline fn any( 356 | self: anytype, 357 | condition: anytype, 358 | ) bool { 359 | var self_copy = self.*; 360 | while (self_copy.next()) |item| { 361 | if (meta.callCallable(bool, condition, .{item})) { 362 | return true; 363 | } 364 | } 365 | return false; 366 | } 367 | 368 | test any { 369 | try expectEqual(false, from("").any(std.ascii.isDigit)); 370 | try expectEqual(false, from("abc").any(std.ascii.isDigit)); 371 | try expectEqual(true, from("abc1").any(std.ascii.isDigit)); 372 | 373 | const Closure = struct { 374 | needle: u8, 375 | pub fn f(self: @This(), item: u8) bool { 376 | return item == self.needle; 377 | } 378 | }; 379 | try expectEqual(true, from("abc_def").any(Closure{ .needle = '_' })); 380 | try expectEqual(false, from("abc_def").any(Closure{ .needle = '1' })); 381 | } 382 | 383 | /// Checks if all items in the sequence satisfy the provided condition. 384 | /// 385 | /// An empty sequence always returns `true`. 386 | pub inline fn all( 387 | self: anytype, 388 | condition: anytype, 389 | ) bool { 390 | var self_copy = self.*; 391 | while (self_copy.next()) |item| { 392 | if (!meta.callCallable(bool, condition, .{item})) { 393 | return false; 394 | } 395 | } 396 | return true; 397 | } 398 | 399 | test all { 400 | try expectEqual(true, from("").all(std.ascii.isDigit)); 401 | try expectEqual(false, from("abc").all(std.ascii.isDigit)); 402 | try expectEqual(false, from("abc123").all(std.ascii.isDigit)); 403 | try expectEqual(true, from("123").all(std.ascii.isDigit)); 404 | 405 | const Closure = struct { 406 | divider: u8, 407 | pub fn hasZeroRemainder(self: @This(), item: u8) bool { 408 | return item % self.divider == 0; 409 | } 410 | }; 411 | try expectEqual(true, from(&[_]u8{ 3, 6, 9 }).all(Closure{ .divider = 3 })); 412 | try expectEqual(false, from(&[_]u8{ 5, 9, 15, 20 }).all(Closure{ .divider = 5 })); 413 | } 414 | 415 | /// Returns the first item of the sequence or `null` if the sequence is empty. 416 | pub inline fn first(self: anytype) ?IteratorItem(@TypeOf(self)) { 417 | var self_copy = self.*; 418 | return self_copy.next() orelse null; 419 | } 420 | 421 | test first { 422 | try expectEqual(@as(?u8, null), from("").first()); 423 | try expectEqual(@as(?u8, 'a'), from("a").first()); 424 | try expectEqual(@as(?u8, 'a'), from("abc").first()); 425 | } 426 | 427 | /// Returns the last item of the sequence or `null` if the sequence is empty. 428 | pub inline fn last(self: anytype) ?IteratorItem(@TypeOf(self)) { 429 | var self_copy = self.*; 430 | var last_item: ?IteratorItem(@TypeOf(self)) = null; 431 | while (self_copy.next()) |item| { 432 | last_item = item; 433 | } 434 | return last_item; 435 | } 436 | 437 | test last { 438 | try expectEqual(@as(?u8, null), from("").last()); 439 | try expectEqual(@as(?u8, 'a'), from("a").last()); 440 | try expectEqual(@as(?u8, 'c'), from("abc").last()); 441 | } 442 | 443 | /// Returns the item at the specified index or `null` if the index is out of bounds. 444 | pub inline fn elementAt( 445 | self: anytype, 446 | index: usize, 447 | ) ?IteratorItem(@TypeOf(self)) { 448 | var self_copy = self.*; 449 | var current_index: usize = 0; 450 | while (current_index < index and self_copy.next() != null) { 451 | current_index += 1; 452 | } 453 | return self_copy.next(); 454 | } 455 | 456 | test elementAt { 457 | try expectEqual(@as(?u8, null), from("").elementAt(0)); 458 | try expectEqual(@as(?u8, 'a'), from("abc").elementAt(0)); 459 | try expectEqual(@as(?u8, 'b'), from("abc").elementAt(1)); 460 | try expectEqual(@as(?u8, 'c'), from("abc").elementAt(2)); 461 | try expectEqual(@as(?u8, null), from("abc").elementAt(3)); 462 | } 463 | 464 | /// Filters the items in the sequence using the given predicate function. 465 | pub inline fn where( 466 | self: anytype, 467 | predicate: anytype, 468 | ) WhereIterator(Iterator(@TypeOf(self)), @TypeOf(predicate)) { 469 | return .{ 470 | .prev_iter = self.*, 471 | .predicate = predicate, 472 | }; 473 | } 474 | 475 | test where { 476 | try expectEqualIter("123", from("a12bcd3ef").where(std.ascii.isDigit)); 477 | try expectEqualIter("", from("abcd").where(std.ascii.isDigit)); 478 | try expectEqualIter("222", from("a12bcd32e2f").where(struct { 479 | needle: u8, 480 | pub fn f(self: @This(), item: u8) bool { 481 | return self.needle == item; 482 | } 483 | }{ .needle = '2' })); 484 | } 485 | 486 | pub fn WhereIterator( 487 | comptime TPrevIter: type, 488 | comptime TPredicate: type, 489 | ) type { 490 | return struct { 491 | const Self = @This(); 492 | const Item = IteratorItem(TPrevIter); 493 | 494 | prev_iter: TPrevIter, 495 | predicate: meta.ConstPtrWhenFn(TPredicate), 496 | 497 | pub fn next(self: *Self) ?Item { 498 | while (self.prev_iter.next()) |item| { 499 | if (meta.callCallable(bool, self.predicate, .{item})) { 500 | return item; 501 | } 502 | } 503 | return null; 504 | } 505 | 506 | pub usingnamespace enumerable; 507 | }; 508 | } 509 | 510 | /// Transforms the items in the sequence using the given selector function. 511 | pub inline fn select( 512 | self: anytype, 513 | selector: anytype, 514 | ) SelectIterator(Iterator(@TypeOf(self)), @TypeOf(selector)) { 515 | return .{ 516 | .prev_iter = self.*, 517 | .selector = selector, 518 | }; 519 | } 520 | 521 | test select { 522 | try expectEqualIter(&[_]bool{ false, true, true, false, false, true }, from("a12bc3").select(std.ascii.isDigit)); 523 | try expectEqualIter(&[_]bool{}, from("").select(std.ascii.isDigit)); 524 | try expectEqualIter("234", from("123").select(struct { 525 | state: u8, 526 | pub fn f(self: @This(), item: u8) u8 { 527 | return item + self.state; 528 | } 529 | }{ .state = 1 })); 530 | } 531 | 532 | pub fn SelectIterator( 533 | comptime TPrevIter: type, 534 | comptime TSelector: type, 535 | ) type { 536 | return struct { 537 | const Self = @This(); 538 | const Item = meta.CallableReturnType(TSelector).?; 539 | 540 | prev_iter: TPrevIter, 541 | selector: meta.ConstPtrWhenFn(TSelector), 542 | 543 | pub fn next(self: *Self) ?Item { 544 | while (self.prev_iter.next()) |item| { 545 | return meta.callCallable(Item, self.selector, .{item}); 546 | } 547 | return null; 548 | } 549 | 550 | pub usingnamespace enumerable; 551 | }; 552 | } 553 | 554 | /// Transforms and flattens the items in the sequence using the given selector function. 555 | pub inline fn selectMany( 556 | self: anytype, 557 | selector: anytype, 558 | ) SelectManyIterator(Iterator(@TypeOf(self)), @TypeOf(selector)) { 559 | return .{ 560 | .prev_iter = self.*, 561 | .slice_iter = null, 562 | .selector = selector, 563 | }; 564 | } 565 | 566 | test selectMany { 567 | try expectEqualIter(&[_]u8{ 0, 1, 0, 2, 1, 0 }, from(&[_]u8{ 1, 2, 3 }).selectMany(countdown2(u8))); 568 | } 569 | 570 | // How to resolve type 571 | pub fn SelectManyIterator( 572 | comptime TPrevIter: type, 573 | comptime TSelector: type, 574 | ) type { 575 | return struct { 576 | const Self = @This(); 577 | const Item = IteratorItem(Iterator(meta.CallableReturnType(TSelector).?)); 578 | 579 | prev_iter: TPrevIter, 580 | slice_iter: ?Iterator(meta.CallableReturnType(TSelector).?), 581 | selector: meta.ConstPtrWhenFn(TSelector), 582 | 583 | pub fn next(self: *Self) ?Item { 584 | if (self.slice_iter == null) { 585 | if (self.prev_iter.next()) |item| { 586 | const slice = meta.callCallable(meta.CallableReturnType(TSelector).?, self.selector, .{item}); 587 | self.slice_iter = from(slice); 588 | } else { 589 | return null; 590 | } 591 | } 592 | if (self.slice_iter.?.next()) |slice_item| { 593 | return slice_item; 594 | } else { 595 | while (self.prev_iter.next()) |item| { 596 | const slice = meta.callCallable(meta.CallableReturnType(TSelector).?, self.selector, .{item}); 597 | self.slice_iter = from(slice); 598 | if (self.slice_iter.?.next()) |slice_item| { 599 | return slice_item; 600 | } 601 | } 602 | } 603 | return null; 604 | } 605 | 606 | pub usingnamespace enumerable; 607 | }; 608 | } 609 | 610 | /// Yields items in a sliding window of a specified size. 611 | pub inline fn window( 612 | self: anytype, 613 | comptime window_size: usize, 614 | ) WindowIterator(Iterator(@TypeOf(self)), window_size) { 615 | return .{ 616 | .prev_iter = self.*, 617 | .maybe_window = null, 618 | }; 619 | } 620 | 621 | test window { 622 | try expectEqualIter( 623 | &[_][3]u8{ 624 | .{ 1, 2, 3 }, 625 | .{ 2, 3, 4 }, 626 | .{ 3, 4, 5 }, 627 | }, 628 | from(&[_]u8{ 1, 2, 3, 4, 5 }).window(3), 629 | ); 630 | try expectEqualIter( 631 | &[_][1]u8{ 632 | .{1}, 633 | .{2}, 634 | .{3}, 635 | .{4}, 636 | .{5}, 637 | }, 638 | from(&[_]u8{ 1, 2, 3, 4, 5 }).window(1), 639 | ); 640 | try expectEqualIter( 641 | &[_][5]u8{ 642 | .{ 1, 2, 3, 4, 5 }, 643 | }, 644 | from(&[_]u8{ 1, 2, 3, 4, 5 }).window(5), 645 | ); 646 | try expectEqualIter( 647 | &[_][6]u8{}, 648 | from(&[_]u8{ 1, 2, 3, 4, 5 }).window(6), 649 | ); 650 | } 651 | 652 | pub fn WindowIterator( 653 | comptime TPrevIter: type, 654 | comptime window_size: usize, 655 | ) type { 656 | return struct { 657 | const Self = @This(); 658 | const Item = IteratorItem(TPrevIter); 659 | 660 | prev_iter: TPrevIter, 661 | maybe_window: ?[window_size]Item, 662 | 663 | pub fn next(self: *Self) ?[window_size]Item { 664 | if (self.maybe_window) |*window_buffer| { 665 | var index: usize = 0; 666 | while (index < window_size - 1) { 667 | window_buffer[index] = window_buffer[index + 1]; 668 | index += 1; 669 | } 670 | if (self.prev_iter.next()) |item| { 671 | window_buffer[index] = item; 672 | return window_buffer.*; 673 | } else { 674 | return null; 675 | } 676 | } else { 677 | self.maybe_window = .{0} ** window_size; 678 | var index: usize = 0; 679 | while (index < window_size) { 680 | if (self.prev_iter.next()) |item| { 681 | self.maybe_window.?[index] = item; 682 | } else { 683 | return null; 684 | } 685 | index += 1; 686 | } 687 | return self.maybe_window; 688 | } 689 | } 690 | 691 | pub usingnamespace enumerable; 692 | }; 693 | } 694 | 695 | /// Aggregates items in the sequence using the given selector function and seed value. 696 | /// Returns an iterator that yields intermediate results. 697 | pub inline fn scan( 698 | self: anytype, 699 | selector: anytype, 700 | seed: meta.CallableReturnType(@TypeOf(selector)).?, 701 | ) ScanIterator(Iterator(@TypeOf(self)), @TypeOf(selector)) { 702 | return .{ 703 | .prev_iter = self.*, 704 | .selector = selector, 705 | .state = seed, 706 | }; 707 | } 708 | 709 | test scan { 710 | const add = struct { 711 | fn f(a: u8, b: u8) u8 { 712 | return a + b; 713 | } 714 | }.f; 715 | try expectEqualIter( 716 | &[_]u8{ 1, 3, 6, 10, 15 }, 717 | from(&[_]u8{ 1, 2, 3, 4, 5 }).scan(add, 0), 718 | ); 719 | } 720 | 721 | pub fn ScanIterator( 722 | comptime TPrevIter: type, 723 | comptime TSelector: type, 724 | ) type { 725 | return struct { 726 | const Self = @This(); 727 | const Item = meta.CallableReturnType(TSelector).?; 728 | 729 | prev_iter: TPrevIter, 730 | state: Item, 731 | selector: meta.ConstPtrWhenFn(TSelector), 732 | 733 | pub fn next(self: *Self) ?Item { 734 | if (self.prev_iter.next()) |item| { 735 | self.state = meta.callCallable(Item, self.selector, .{ self.state, item }); 736 | return self.state; 737 | } 738 | return null; 739 | } 740 | 741 | pub usingnamespace enumerable; 742 | }; 743 | } 744 | 745 | /// Aggregates items in the sequence using the given selector function and seed value. 746 | /// Finishes iterations immediately and returns the final result. 747 | pub inline fn aggregate( 748 | self: anytype, 749 | selector: anytype, 750 | seed: meta.CallableReturnType(@TypeOf(selector)).?, 751 | ) meta.CallableReturnType(@TypeOf(selector)).? { 752 | const TResult = meta.CallableReturnType(@TypeOf(selector)).?; 753 | var self_copy = self.*; 754 | var result = seed; 755 | while (self_copy.next()) |item| { 756 | result = meta.callCallable(TResult, selector, .{ result, item }); 757 | } 758 | return result; 759 | } 760 | 761 | test aggregate { 762 | const add = struct { 763 | fn f(a: u8, b: u8) u8 { 764 | return a + b; 765 | } 766 | }.f; 767 | try std.testing.expectEqual( 768 | @as(u8, 15), 769 | from(&[_]u8{ 1, 2, 3, 4, 5 }).aggregate(add, 0), 770 | ); 771 | } 772 | 773 | /// Determines whether the sequence contains a specified `needle` item. 774 | pub inline fn contains( 775 | self: anytype, 776 | needle: IteratorItem(@TypeOf(self)), 777 | ) bool { 778 | var self_copy = self.*; 779 | while (self_copy.next()) |item| { 780 | if (item == needle) { 781 | return true; 782 | } 783 | } 784 | return false; 785 | } 786 | test contains { 787 | try std.testing.expectEqual(true, from(&[_]u8{ 1, 2, 3 }).contains(2)); 788 | try std.testing.expectEqual(false, from(&[_]u8{ 1, 2, 3 }).contains(4)); 789 | } 790 | 791 | /// Determines whether the two sequences are equal. 792 | pub inline fn equals( 793 | self: anytype, 794 | other_iter: anytype, 795 | ) bool { 796 | var self_copy = self.*; 797 | var other_copy = other_iter; 798 | while (true) { 799 | const first_item = self_copy.next(); 800 | const second_item = other_copy.next(); 801 | 802 | if (first_item == null and second_item == null) { 803 | return true; 804 | } 805 | 806 | if (first_item != second_item) { 807 | return false; 808 | } 809 | } 810 | } 811 | 812 | test equals { 813 | try std.testing.expect( 814 | from(&[_]u8{ 1, 2, 3 }).equals(from(&[_]u8{ 1, 2, 3 })), 815 | ); 816 | try std.testing.expect( 817 | !from(&[_]u8{ 1, 2, 3 }).equals(from(&[_]u8{ 1, 2, 3, 4 })), 818 | ); 819 | try std.testing.expect( 820 | !from(&[_]u8{ 1, 2 }).equals(from(&[_]u8{ 1, 2, 3 })), 821 | ); 822 | try std.testing.expect( 823 | !from(&[_]u8{ 1, 2, 3 }).equals(from(&[_]u8{ 1, 2, 4 })), 824 | ); 825 | } 826 | 827 | /// Returns the index of the first occurrence of a `needle` value in the sequence 828 | /// or `null` if value is not found. 829 | pub inline fn indexOf(self: anytype, needle: IteratorItem(@TypeOf(self))) ?usize { 830 | var self_copy = self.*; 831 | var index: usize = 0; 832 | while (self_copy.next()) |item| { 833 | if (item == needle) { 834 | return index; 835 | } 836 | index += 1; 837 | } 838 | return null; 839 | } 840 | 841 | test indexOf { 842 | try std.testing.expectEqual(@as(?usize, 0), from(&[_]u8{ 1, 2, 3, 4, 5 }).indexOf(1)); 843 | try std.testing.expectEqual(@as(?usize, 2), from("abcd").indexOf('c')); 844 | try std.testing.expectEqual(@as(?usize, 4), from(&[_]u8{ 1, 2, 3, 4, 5 }).indexOf(5)); 845 | try std.testing.expectEqual(@as(?usize, null), from(&[_]u8{ 1, 2, 3, 4, 5 }).indexOf(7)); 846 | } 847 | 848 | /// Concatenates two sequences. 849 | pub inline fn concat( 850 | self: anytype, 851 | other: anytype, 852 | ) ConcatIterator(@TypeOf(self.*), @TypeOf(from(other))) { 853 | return .{ 854 | .prev_iter = self.*, 855 | .next_iter = from(other), 856 | }; 857 | } 858 | 859 | test concat { 860 | try expectEqualIter("abcdef", from("abc").concat(from("def"))); 861 | try expectEqualIter("abcdef", from("abc").concat("def")); 862 | } 863 | 864 | pub fn ConcatIterator( 865 | comptime TPrevIter: type, 866 | comptime TNextIter: type, 867 | ) type { 868 | return struct { 869 | const Self = @This(); 870 | const Item = IteratorItem(TPrevIter); 871 | 872 | prev_iter: TPrevIter, 873 | next_iter: TNextIter, 874 | 875 | pub fn next(self: *Self) ?Item { 876 | return self.prev_iter.next() orelse self.next_iter.next() orelse null; 877 | } 878 | 879 | pub usingnamespace enumerable; 880 | }; 881 | } 882 | 883 | /// Filters the items in the sequence to exclude all items from the other sequence. 884 | pub inline fn except( 885 | self: anytype, 886 | other: anytype, 887 | ) ExceptIterator(@TypeOf(self.*), @TypeOf(from(other))) { 888 | return .{ 889 | .prev_iter = self.*, 890 | .other_iter = from(other), 891 | }; 892 | } 893 | 894 | test except { 895 | try expectEqualIter("ace", from("abcdef").except("bdf")); 896 | try expectEqualIter("", from("abc").except("abc")); 897 | try expectEqualIter("", from("abc").except("cba")); 898 | try expectEqualIter("abc", from("abc").except("def")); 899 | try expectEqualIter("", from("").except("abc")); 900 | } 901 | 902 | pub fn ExceptIterator( 903 | comptime TPrevIter: type, 904 | comptime TOtherIter: type, 905 | ) type { 906 | return struct { 907 | const Self = @This(); 908 | const Item = IteratorItem(TPrevIter); 909 | 910 | prev_iter: TPrevIter, 911 | other_iter: TOtherIter, 912 | 913 | pub fn next(self: *Self) ?Item { 914 | while (self.prev_iter.next()) |item| { 915 | if (!self.other_iter.contains(item)) { 916 | return item; 917 | } 918 | } 919 | return null; 920 | } 921 | 922 | pub usingnamespace enumerable; 923 | }; 924 | } 925 | 926 | /// Combines the items of two sequences into a single sequence of pairs. 927 | /// 928 | /// The first element of the pair comes from the first sequence, and the second element comes from the second sequence. 929 | pub inline fn zip( 930 | self: anytype, 931 | other_iter: anytype, 932 | ) ZipIterator(@TypeOf(self.*), @TypeOf(other_iter)) { 933 | return .{ 934 | .prev_iter = self.*, 935 | .other_iter = other_iter, 936 | }; 937 | } 938 | 939 | test zip { 940 | try expectEqualIter( 941 | &[_]struct { u8, u8 }{ .{ 1, 4 }, .{ 2, 5 }, .{ 3, 6 } }, 942 | from(&[_]u8{ 1, 2, 3 }).zip(from(&[_]u8{ 4, 5, 6 })), 943 | ); 944 | } 945 | 946 | pub fn ZipIterator( 947 | comptime TPrevIter: type, 948 | comptime TOtherIter: type, 949 | ) type { 950 | return struct { 951 | const Self = @This(); 952 | const Item = IteratorItem(TPrevIter); 953 | const OtherItem = IteratorItem(TOtherIter); 954 | 955 | prev_iter: TPrevIter, 956 | other_iter: TOtherIter, 957 | 958 | pub fn next(self: *Self) ?struct { Item, OtherItem } { 959 | if (self.prev_iter.next()) |first_item| { 960 | if (self.other_iter.next()) |second_item| { 961 | return .{ first_item, second_item }; 962 | } 963 | } 964 | return null; 965 | } 966 | 967 | pub usingnamespace enumerable; 968 | }; 969 | } 970 | 971 | /// Appends an item to the end of the sequence. 972 | pub inline fn append( 973 | self: anytype, 974 | item: IteratorItem(@TypeOf(self)), 975 | ) AppendIterator(@TypeOf(self.*)) { 976 | return .{ 977 | .prev_iter = self.*, 978 | .appended_item = item, 979 | }; 980 | } 981 | 982 | test append { 983 | try expectEqualIter("abc", from("ab").append('c')); 984 | } 985 | 986 | pub fn AppendIterator( 987 | comptime TPrevIter: type, 988 | ) type { 989 | return struct { 990 | const Self = @This(); 991 | const Item = IteratorItem(TPrevIter); 992 | 993 | prev_iter: TPrevIter, 994 | appended_item: ?Item, 995 | 996 | pub fn next(self: *Self) ?Item { 997 | if (self.prev_iter.next()) |item| { 998 | return item; 999 | } else if (self.appended_item) |appended_item| { 1000 | self.appended_item = null; 1001 | return appended_item; 1002 | } else { 1003 | return null; 1004 | } 1005 | } 1006 | 1007 | pub usingnamespace enumerable; 1008 | }; 1009 | } 1010 | 1011 | /// Prepends an item to the start of the sequence. 1012 | pub inline fn prepend( 1013 | self: anytype, 1014 | item: IteratorItem(@TypeOf(self)), 1015 | ) PrependIterator(@TypeOf(self.*)) { 1016 | return .{ 1017 | .prev_iter = self.*, 1018 | .prepended_item = item, 1019 | }; 1020 | } 1021 | 1022 | test prepend { 1023 | try expectEqualIter("abc", from("bc").prepend('a')); 1024 | } 1025 | 1026 | pub fn PrependIterator( 1027 | comptime TPrevIter: type, 1028 | ) type { 1029 | return struct { 1030 | const Self = @This(); 1031 | const Item = IteratorItem(TPrevIter); 1032 | 1033 | prev_iter: TPrevIter, 1034 | prepended_item: ?Item, 1035 | 1036 | pub fn next(self: *Self) ?Item { 1037 | if (self.prepended_item) |appended_item| { 1038 | self.prepended_item = null; 1039 | return appended_item; 1040 | } else if (self.prev_iter.next()) |item| { 1041 | return item; 1042 | } else { 1043 | return null; 1044 | } 1045 | } 1046 | 1047 | pub usingnamespace enumerable; 1048 | }; 1049 | } 1050 | 1051 | /// Adds a separator item between adjacent items of the original sequence. 1052 | pub inline fn intersperse( 1053 | self: anytype, 1054 | separator: IteratorItem(@TypeOf(self)), 1055 | ) IntersperseIterator(@TypeOf(self.*)) { 1056 | return .{ 1057 | .prev_iter = self.peekable(), 1058 | .delimiter = separator, 1059 | .next_is_delimiter = false, 1060 | }; 1061 | } 1062 | 1063 | test intersperse { 1064 | try expectEqualIter("a_b_c_d", from("abcd").intersperse('_')); 1065 | } 1066 | 1067 | pub fn IntersperseIterator( 1068 | comptime TPrevIter: type, 1069 | ) type { 1070 | return struct { 1071 | const Self = @This(); 1072 | const Item = IteratorItem(TPrevIter); 1073 | 1074 | prev_iter: PeekableIterator(TPrevIter), 1075 | delimiter: ?Item, 1076 | next_is_delimiter: bool, 1077 | 1078 | pub fn next(self: *Self) ?Item { 1079 | if (self.prev_iter.peek()) |_| { 1080 | if (self.next_is_delimiter) { 1081 | self.next_is_delimiter = false; 1082 | return self.delimiter; 1083 | } else { 1084 | self.next_is_delimiter = true; 1085 | return self.prev_iter.next(); 1086 | } 1087 | } 1088 | return null; 1089 | } 1090 | 1091 | pub usingnamespace enumerable; 1092 | }; 1093 | } 1094 | 1095 | /// Returns an iterator, which can peek the next value without advancing the sequence. 1096 | pub inline fn peekable( 1097 | self: anytype, 1098 | ) PeekableIterator(@TypeOf(self.*)) { 1099 | return .{ 1100 | .prev_iter = self.*, 1101 | .peeked_item = null, 1102 | }; 1103 | } 1104 | 1105 | pub fn PeekableIterator( 1106 | comptime TPrevIter: type, 1107 | ) type { 1108 | return struct { 1109 | const Self = @This(); 1110 | const Item = IteratorItem(TPrevIter); 1111 | 1112 | prev_iter: TPrevIter, 1113 | peeked_item: ??Item, 1114 | 1115 | pub fn next(self: *Self) ?Item { 1116 | if (self.peeked_item) |item| { 1117 | self.peeked_item = null; 1118 | return item; 1119 | } 1120 | return self.prev_iter.next(); 1121 | } 1122 | 1123 | pub fn peek(self: *Self) ?Item { 1124 | if (self.peeked_item) |item| { 1125 | return item; 1126 | } else if (self.next()) |item| { 1127 | self.peeked_item = item; 1128 | return item; 1129 | } else { 1130 | return null; 1131 | } 1132 | } 1133 | 1134 | pub usingnamespace enumerable; 1135 | }; 1136 | } 1137 | 1138 | /// Return the specified number of items from the start of the sequence, 1139 | /// or fewer if the sequence finishes sooner. 1140 | pub inline fn take( 1141 | self: anytype, 1142 | item_count: usize, 1143 | ) TakeIterator(@TypeOf(self.*)) { 1144 | return .{ 1145 | .prev_iter = self.*, 1146 | .index = 0, 1147 | .count = item_count, 1148 | }; 1149 | } 1150 | 1151 | test take { 1152 | try expectEqualIter("abc", from("abcdef").take(3)); 1153 | try expectEqualIter("abc", from("abc").take(6)); 1154 | } 1155 | 1156 | pub fn TakeIterator( 1157 | comptime TPrevIter: type, 1158 | ) type { 1159 | return struct { 1160 | const Self = @This(); 1161 | const Item = IteratorItem(TPrevIter); 1162 | 1163 | prev_iter: TPrevIter, 1164 | count: usize, 1165 | index: usize, 1166 | 1167 | pub fn next(self: *Self) ?Item { 1168 | if (self.index < self.count) { 1169 | self.index += 1; 1170 | return self.prev_iter.next() orelse null; 1171 | } 1172 | return null; 1173 | } 1174 | 1175 | pub usingnamespace enumerable; 1176 | }; 1177 | } 1178 | 1179 | /// Return every nth item of the sequence. 1180 | pub inline fn takeEvery( 1181 | self: anytype, 1182 | every_nth: usize, 1183 | ) TakeEveryIterator(@TypeOf(self.*)) { 1184 | return .{ 1185 | .prev_iter = self.*, 1186 | .every_nth = every_nth, 1187 | }; 1188 | } 1189 | 1190 | test takeEvery { 1191 | try expectEqualIter("2468", from("123456789").takeEvery(2)); 1192 | } 1193 | 1194 | pub fn TakeEveryIterator( 1195 | comptime TPrevIter: type, 1196 | ) type { 1197 | return struct { 1198 | const Self = @This(); 1199 | const Item = IteratorItem(TPrevIter); 1200 | 1201 | prev_iter: TPrevIter, 1202 | every_nth: usize, 1203 | 1204 | pub fn next(self: *Self) ?Item { 1205 | var skipped: usize = 0; 1206 | while (skipped < self.every_nth - 1) { 1207 | _ = self.prev_iter.next(); 1208 | skipped += 1; 1209 | } 1210 | return self.prev_iter.next(); 1211 | } 1212 | 1213 | pub usingnamespace enumerable; 1214 | }; 1215 | } 1216 | 1217 | /// Returns items as long as the condition function returns `true`. 1218 | /// Stops when the condition function returns `false` for the first time. 1219 | pub inline fn takeWhile( 1220 | self: anytype, 1221 | predicate: anytype, 1222 | ) TakeWhileIterator(@TypeOf(self.*), @TypeOf(predicate)) { 1223 | return .{ 1224 | .prev_iter = self.*, 1225 | .completed = false, 1226 | .predicate = predicate, 1227 | }; 1228 | } 1229 | 1230 | test takeWhile { 1231 | const even = struct { 1232 | fn f(number: u8) bool { 1233 | return @rem(number, 2) == 0; 1234 | } 1235 | }.f; 1236 | try expectEqualIter( 1237 | &[_]u8{ 2, 4, 6 }, 1238 | from(&[_]u8{ 2, 4, 6, 7, 8, 9, 10 }).takeWhile(even), 1239 | ); 1240 | try expectEqualIter( 1241 | &[_]u8{ 2, 4, 6, 8, 10 }, 1242 | from(&[_]u8{ 2, 4, 6, 8, 10 }).takeWhile(even), 1243 | ); 1244 | try expectEqualIter( 1245 | &[_]u8{}, 1246 | from(&[_]u8{ 1, 2, 3, 4, 5 }).takeWhile(even), 1247 | ); 1248 | } 1249 | 1250 | pub fn TakeWhileIterator( 1251 | comptime TPrevIter: type, 1252 | comptime TSelector: type, 1253 | ) type { 1254 | return struct { 1255 | const Self = @This(); 1256 | const Item = IteratorItem(TPrevIter); 1257 | 1258 | prev_iter: TPrevIter, 1259 | completed: bool, 1260 | predicate: meta.ConstPtrWhenFn(TSelector), 1261 | 1262 | pub fn next(self: *Self) ?Item { 1263 | if (self.completed) { 1264 | return null; 1265 | } else if (self.prev_iter.next()) |item| { 1266 | if (meta.callCallable(bool, self.predicate, .{item})) { 1267 | return item; 1268 | } else { 1269 | self.completed = true; 1270 | return null; 1271 | } 1272 | } else { 1273 | return null; 1274 | } 1275 | } 1276 | 1277 | pub usingnamespace enumerable; 1278 | }; 1279 | } 1280 | 1281 | /// Collects the sequence into an array list. 1282 | pub inline fn toArrayList( 1283 | self: anytype, 1284 | allocator: std.mem.Allocator, 1285 | ) !std.ArrayList(IteratorItem(@TypeOf(self))) { 1286 | var self_copy = self.*; 1287 | var array_list = std.ArrayList(IteratorItem(@TypeOf(self))).init(allocator); 1288 | while (self_copy.next()) |item| { 1289 | try array_list.append(item); 1290 | } 1291 | return array_list; 1292 | } 1293 | 1294 | test toArrayList { 1295 | var arrayList = try countdown(@as(u8, 4)).toArrayList(std.testing.allocator); 1296 | defer arrayList.deinit(); 1297 | try std.testing.expectEqualSlices(u8, &[_]u8{ 3, 2, 1, 0 }, arrayList.items); 1298 | } 1299 | 1300 | /// Skips a specified number of items in the sequence and then returns the remaining items. 1301 | pub inline fn skip( 1302 | self: anytype, 1303 | item_count: usize, 1304 | ) SkipIterator(@TypeOf(self.*)) { 1305 | return .{ 1306 | .prev_iter = self.*, 1307 | .index = 0, 1308 | .count = item_count, 1309 | }; 1310 | } 1311 | 1312 | test skip { 1313 | try expectEqualIter( 1314 | &[_]u8{ 4, 5, 6, 7, 8, 9, 10 }, 1315 | from(&[_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).skip(3), 1316 | ); 1317 | try expectEqualIter( 1318 | &[_]u8{}, 1319 | from(&[_]u8{ 1, 2, 3 }).skip(3), 1320 | ); 1321 | } 1322 | 1323 | pub fn SkipIterator( 1324 | comptime TPrevIter: type, 1325 | ) type { 1326 | return struct { 1327 | const Self = @This(); 1328 | const Item = IteratorItem(TPrevIter); 1329 | 1330 | prev_iter: TPrevIter, 1331 | count: usize, 1332 | index: usize, 1333 | 1334 | pub fn next(self: *Self) ?Item { 1335 | while (self.index < self.count) { 1336 | _ = self.prev_iter.next(); 1337 | self.index += 1; 1338 | } 1339 | return self.prev_iter.next() orelse null; 1340 | } 1341 | 1342 | pub usingnamespace enumerable; 1343 | }; 1344 | } 1345 | 1346 | /// Skips items while the condition function returns `true` and then returns the remaining items. 1347 | pub inline fn skipWhile( 1348 | self: anytype, 1349 | predicate: anytype, 1350 | ) SkipWhileIterator(@TypeOf(self.*), @TypeOf(predicate)) { 1351 | return .{ 1352 | .prev_iter = self.*, 1353 | .skipping_is_done = false, 1354 | .predicate = predicate, 1355 | }; 1356 | } 1357 | 1358 | test skipWhile { 1359 | const even = struct { 1360 | fn f(number: u8) bool { 1361 | return @rem(number, 2) == 0; 1362 | } 1363 | }.f; 1364 | try expectEqualIter( 1365 | &[_]u8{ 7, 8, 9, 10 }, 1366 | from(&[_]u8{ 2, 4, 6, 7, 8, 9, 10 }).skipWhile(even), 1367 | ); 1368 | try expectEqualIter( 1369 | &[_]u8{}, 1370 | from(&[_]u8{ 2, 4, 6, 8 }).skipWhile(even), 1371 | ); 1372 | try expectEqualIter( 1373 | &[_]u8{ 1, 2, 3, 4 }, 1374 | from(&[_]u8{ 1, 2, 3, 4 }).skipWhile(even), 1375 | ); 1376 | } 1377 | 1378 | pub fn SkipWhileIterator( 1379 | comptime TPrevIter: type, 1380 | comptime TPredicate: type, 1381 | ) type { 1382 | return struct { 1383 | const Self = @This(); 1384 | const Item = IteratorItem(TPrevIter); 1385 | 1386 | prev_iter: TPrevIter, 1387 | skipping_is_done: bool, 1388 | predicate: meta.ConstPtrWhenFn(TPredicate), 1389 | 1390 | pub fn next(self: *Self) ?Item { 1391 | if (!self.skipping_is_done) { 1392 | while (self.prev_iter.next()) |item| { 1393 | if (!meta.callCallable(bool, self.predicate, .{item})) { 1394 | self.skipping_is_done = true; 1395 | return item; 1396 | } 1397 | } 1398 | } 1399 | 1400 | return self.prev_iter.next(); 1401 | } 1402 | 1403 | pub usingnamespace enumerable; 1404 | }; 1405 | } 1406 | 1407 | /// Calls a function on each item of the sequence. 1408 | pub inline fn forEach( 1409 | self: anytype, 1410 | function: anytype, 1411 | ) void { 1412 | const TFunction = meta.ConstPtrWhenFn(@TypeOf(function)); 1413 | const TReturn = meta.CallableReturnType(TFunction).?; 1414 | var self_copy = self.*; 1415 | while (self_copy.next()) |item| { 1416 | _ = meta.callCallable(TReturn, function, .{item}); 1417 | function(item); 1418 | } 1419 | } 1420 | test forEach { 1421 | const printItem = struct { 1422 | fn f(item: u8) void { 1423 | std.debug.print("\n Item: {}", .{item}); 1424 | } 1425 | }.f; 1426 | var iter = from(&[_]u8{ 1, 2, 3 }); 1427 | iter.forEach(printItem); 1428 | } 1429 | 1430 | /// Calls a function on each item of the sequence and yields the item. 1431 | /// 1432 | /// Useful for inspecting a chain of iterators at any particular point. 1433 | pub inline fn inspect( 1434 | self: anytype, 1435 | function: anytype, 1436 | ) InspectIterator(@TypeOf(self.*), @TypeOf(function)) { 1437 | return .{ 1438 | .prev_iter = self.*, 1439 | .function = function, 1440 | }; 1441 | } 1442 | 1443 | test inspect { 1444 | const printItem = struct { 1445 | fn f(item: u8) void { 1446 | std.debug.print("\n Item: {}", .{item}); 1447 | } 1448 | }.f; 1449 | _ = from(&[_]u8{ 1, 2, 3 }).inspect(printItem); 1450 | } 1451 | 1452 | pub fn InspectIterator( 1453 | comptime TPrevIter: type, 1454 | comptime TFunction: type, 1455 | ) type { 1456 | return struct { 1457 | const Self = @This(); 1458 | const Item = IteratorItem(TPrevIter); 1459 | 1460 | prev_iter: TPrevIter, 1461 | function: meta.ConstPtrWhenFn(TFunction), 1462 | 1463 | pub fn next(self: *Self) ?Item { 1464 | if (self.prev_iter.next()) |item| { 1465 | const TReturn = meta.CallableReturnType(@TypeOf(self.function)).?; 1466 | meta.callCallable(TReturn, self.function, .{item}); 1467 | return item; 1468 | } 1469 | return null; 1470 | } 1471 | 1472 | pub usingnamespace enumerable; 1473 | }; 1474 | } 1475 | 1476 | /// Returns the maximum item in a sequence. 1477 | /// 1478 | /// Returns `null` if the sequence is empty. 1479 | pub inline fn max(self: anytype) ?IteratorItem(@TypeOf(self)) { 1480 | var self_copy = self.*; 1481 | var maybe_max_value: ?IteratorItem(@TypeOf(self)) = null; 1482 | while (self_copy.next()) |item| { 1483 | if (maybe_max_value == null or item > maybe_max_value.?) { 1484 | maybe_max_value = item; 1485 | } 1486 | } 1487 | return maybe_max_value; 1488 | } 1489 | 1490 | test max { 1491 | try std.testing.expectEqual(@as(?u8, 5), from(&[_]u8{ 1, 2, 3, 4, 5 }).max()); 1492 | try std.testing.expectEqual(@as(?u8, 6), from(&[_]u8{ 1, 6, 3, 4, 5 }).max()); 1493 | try std.testing.expectEqual(@as(?u8, null), from(&[_]u8{}).max()); 1494 | } 1495 | 1496 | /// Returns the maximum item of a sequence as determined by the value returned from the selector function. 1497 | /// 1498 | /// Returns `null` if the sequence is empty. 1499 | pub inline fn maxBy( 1500 | self: anytype, 1501 | selector: anytype, 1502 | ) ?IteratorItem(@TypeOf(self)) { 1503 | const TSelectorReturn = meta.CallableReturnType(@TypeOf(selector)).?; 1504 | var self_copy = self.*; 1505 | var maybe_max_item: ?IteratorItem(@TypeOf(self)) = null; 1506 | var maybe_max_value: ?TSelectorReturn = null; 1507 | while (self_copy.next()) |item| { 1508 | const current_value = meta.callCallable(TSelectorReturn, selector, .{item}); 1509 | if (maybe_max_value == null or current_value > maybe_max_value.?) { 1510 | maybe_max_item = item; 1511 | maybe_max_value = current_value; 1512 | } 1513 | } 1514 | return maybe_max_item; 1515 | } 1516 | 1517 | test maxBy { 1518 | const Person = struct { 1519 | name: []const u8, 1520 | age: u8, 1521 | pub fn getAge(self: @This()) u8 { 1522 | return self.age; 1523 | } 1524 | }; 1525 | try std.testing.expectEqual( 1526 | Person{ .name = "John", .age = 5 }, 1527 | from(&[_]Person{ 1528 | .{ .name = "Marry", .age = 1 }, .{ .name = "Dave", .age = 2 }, 1529 | .{ .name = "Gerthrude", .age = 3 }, .{ .name = "Casper", .age = 4 }, 1530 | .{ .name = "John", .age = 5 }, 1531 | }).maxBy(Person.getAge).?, 1532 | ); 1533 | } 1534 | 1535 | /// Returns the minimum item of a sequence. 1536 | /// 1537 | /// Returns `null` if the sequence is empty. 1538 | pub inline fn min(self: anytype) ?IteratorItem(@TypeOf(self)) { 1539 | const TItem = IteratorItem(@TypeOf(self)); 1540 | var self_copy = self.*; 1541 | var maybe_min_value: ?TItem = null; 1542 | while (self_copy.next()) |item| { 1543 | if (maybe_min_value == null or item < maybe_min_value.?) { 1544 | maybe_min_value = item; 1545 | } 1546 | } 1547 | return maybe_min_value; 1548 | } 1549 | 1550 | test min { 1551 | try std.testing.expectEqual(@as(?u8, 1), from(&[_]u8{ 3, 4, 2, 1, 5 }).min()); 1552 | try std.testing.expectEqual(@as(?u8, null), from(&[_]u8{}).min()); 1553 | } 1554 | 1555 | /// Returns the minimum item of an sequence as determined by the value returned from the specified function. 1556 | /// 1557 | /// Returns `null` if the sequence is empty. 1558 | pub inline fn minBy( 1559 | self: anytype, 1560 | selector: anytype, 1561 | ) ?IteratorItem(@TypeOf(self)) { 1562 | const TSelectorReturn = meta.CallableReturnType(@TypeOf(selector)).?; 1563 | var self_copy = self.*; 1564 | var maybe_min_item: ?IteratorItem(@TypeOf(self)) = null; 1565 | var maybe_min_value: ?TSelectorReturn = null; 1566 | while (self_copy.next()) |item| { 1567 | const current_value = meta.callCallable(TSelectorReturn, selector, .{item}); 1568 | if (maybe_min_value == null or current_value < maybe_min_value.?) { 1569 | maybe_min_item = item; 1570 | maybe_min_value = current_value; 1571 | } 1572 | } 1573 | return maybe_min_item; 1574 | } 1575 | 1576 | test minBy { 1577 | const Person = struct { 1578 | name: []const u8, 1579 | age: u8, 1580 | pub fn getAge(self: @This()) u8 { 1581 | return self.age; 1582 | } 1583 | }; 1584 | try std.testing.expectEqual( 1585 | Person{ .name = "Marry", .age = 1 }, 1586 | from(&[_]Person{ 1587 | .{ .name = "Gerthrude", .age = 3 }, .{ .name = "Casper", .age = 4 }, 1588 | .{ .name = "Marry", .age = 1 }, .{ .name = "Dave", .age = 2 }, 1589 | .{ .name = "John", .age = 5 }, 1590 | }).minBy(Person.getAge).?, 1591 | ); 1592 | try std.testing.expectEqual( 1593 | @as(?Person, null), 1594 | from(&[_]Person{}).minBy(Person.getAge), 1595 | ); 1596 | } 1597 | 1598 | /// Namespace for functions that require mutable and indexable iterators. 1599 | const enumerable_mutable_and_indexable = struct { 1600 | fn lessThan(_: void, lhs: u8, rhs: u8) bool { 1601 | return lhs < rhs; 1602 | } 1603 | pub inline fn orderAscending(self: anytype) @TypeOf(self.*) { 1604 | std.mem.sort(IteratorItem(@TypeOf(self)), self.slice, {}, lessThan); 1605 | return self.*; 1606 | } 1607 | test orderAscending { 1608 | var input = std.BoundedArray(u8, 8){}; 1609 | try input.appendSlice("cbdafghe"); 1610 | try expectEqualIter("abcdefgh", enumerable.from(input.slice()).orderAscending()); 1611 | } 1612 | }; 1613 | 1614 | /// Returns whether the items in the sequence are sorted in ascending order. 1615 | pub inline fn isSortedAscending(self: anytype) bool { 1616 | var self_copy = self.*; 1617 | var maybe_previous: ?IteratorItem(@TypeOf(self)) = null; 1618 | while (self_copy.next()) |item| { 1619 | if (maybe_previous) |previous| { 1620 | if (previous > item) { 1621 | return false; 1622 | } 1623 | } 1624 | maybe_previous = item; 1625 | } 1626 | return true; 1627 | } 1628 | 1629 | test isSortedAscending { 1630 | try std.testing.expectEqual(true, from(&[_]u8{ 1, 2, 3 }).isSortedAscending()); 1631 | try std.testing.expectEqual(false, from(&[_]u8{ 1, 3, 2 }).isSortedAscending()); 1632 | } 1633 | 1634 | /// Returns whether the items in the sequence are sorted in descending order. 1635 | pub inline fn isSortedDescending(self: anytype) bool { 1636 | var self_copy = self.*; 1637 | var maybe_previous: ?IteratorItem(@TypeOf(self)) = null; 1638 | while (self_copy.next()) |item| { 1639 | if (maybe_previous) |previous| { 1640 | if (previous < item) { 1641 | return false; 1642 | } 1643 | } 1644 | maybe_previous = item; 1645 | } 1646 | return true; 1647 | } 1648 | 1649 | test isSortedDescending { 1650 | try std.testing.expectEqual(true, from(&[_]u8{ 3, 2, 1 }).isSortedDescending()); 1651 | try std.testing.expectEqual(false, from(&[_]u8{ 3, 1, 2 }).isSortedDescending()); 1652 | } 1653 | 1654 | /// Returns whether the items in the sequence are sorted in ascending order 1655 | /// based on the values returned from the specified selector. 1656 | pub inline fn isSortedAscendingBy( 1657 | self: anytype, 1658 | selector: anytype, 1659 | ) bool { 1660 | const TSelectorReturn = meta.CallableReturnType(@TypeOf(selector)).?; 1661 | var self_copy = self.*; 1662 | var maybe_previous_value: ?TSelectorReturn = null; 1663 | while (self_copy.next()) |current_item| { 1664 | const current_value = meta.callCallable(TSelectorReturn, selector, .{current_item}); 1665 | if (maybe_previous_value) |previous_value| { 1666 | if (previous_value > current_value) { 1667 | return false; 1668 | } 1669 | } 1670 | maybe_previous_value = current_value; 1671 | } 1672 | return true; 1673 | } 1674 | 1675 | test isSortedAscendingBy { 1676 | const Person = struct { 1677 | name: []const u8, 1678 | age: u8, 1679 | pub fn getAge(self: @This()) u8 { 1680 | return self.age; 1681 | } 1682 | }; 1683 | try std.testing.expectEqual( 1684 | true, 1685 | from(&[_]Person{ 1686 | .{ .name = "Marry", .age = 1 }, 1687 | .{ .name = "Dave", .age = 2 }, 1688 | .{ .name = "Gerthrude", .age = 3 }, 1689 | .{ .name = "Casper", .age = 4 }, 1690 | .{ .name = "John", .age = 5 }, 1691 | }).isSortedAscendingBy(Person.getAge), 1692 | ); 1693 | try std.testing.expectEqual( 1694 | false, 1695 | from(&[_]Person{ 1696 | .{ .name = "Marry", .age = 1 }, 1697 | .{ .name = "Dave", .age = 3 }, 1698 | .{ .name = "Gerthrude", .age = 2 }, 1699 | .{ .name = "Casper", .age = 1 }, 1700 | .{ .name = "John", .age = 5 }, 1701 | }).isSortedAscendingBy(Person.getAge), 1702 | ); 1703 | } 1704 | 1705 | /// Returns whether the items in the sequence are sorted in descending order 1706 | /// based on the values returned from the specified selector. 1707 | pub inline fn isSortedDescendingBy( 1708 | self: anytype, 1709 | selector: anytype, 1710 | ) bool { 1711 | const TSelectorReturn = meta.CallableReturnType(@TypeOf(selector)).?; 1712 | var self_copy = self.*; 1713 | var maybe_previous_value: ?TSelectorReturn = null; 1714 | while (self_copy.next()) |current_item| { 1715 | const current_value = meta.callCallable(TSelectorReturn, selector, .{current_item}); 1716 | if (maybe_previous_value) |previous_value| { 1717 | if (previous_value < current_value) { 1718 | return false; 1719 | } 1720 | } 1721 | maybe_previous_value = current_value; 1722 | } 1723 | return true; 1724 | } 1725 | 1726 | test isSortedDescendingBy { 1727 | const Person = struct { 1728 | name: []const u8, 1729 | age: u8, 1730 | pub fn getAge(self: @This()) u8 { 1731 | return self.age; 1732 | } 1733 | }; 1734 | try std.testing.expectEqual( 1735 | true, 1736 | from(&[_]Person{ 1737 | .{ .name = "Marry", .age = 5 }, 1738 | .{ .name = "Dave", .age = 4 }, 1739 | .{ .name = "Gerthrude", .age = 3 }, 1740 | .{ .name = "Casper", .age = 2 }, 1741 | .{ .name = "John", .age = 1 }, 1742 | }).isSortedDescendingBy(Person.getAge), 1743 | ); 1744 | try std.testing.expectEqual( 1745 | false, 1746 | from(&[_]Person{ 1747 | .{ .name = "Marry", .age = 3 }, 1748 | .{ .name = "Dave", .age = 1 }, 1749 | .{ .name = "Gerthrude", .age = 2 }, 1750 | .{ .name = "Casper", .age = 1 }, 1751 | .{ .name = "John", .age = 5 }, 1752 | }).isSortedAscendingBy(Person.getAge), 1753 | ); 1754 | } 1755 | 1756 | /// Returns the average value of all the numbers in the sequence. 1757 | pub inline fn average(self: anytype) IteratorItem(@TypeOf(self)) { 1758 | const TItem = IteratorItem(@TypeOf(self)); 1759 | var self_copy = self.*; 1760 | var total_sum: TItem = 0; 1761 | var total_count: usize = 0; 1762 | while (self_copy.next()) |item| { 1763 | total_sum += item; 1764 | total_count += 1; 1765 | } 1766 | const item_type_info = @typeInfo(TItem); 1767 | if (item_type_info == .Float) { 1768 | return total_sum / @as(TItem, @floatFromInt(total_count)); 1769 | } else if (item_type_info == .Int) { 1770 | if (item_type_info.Int.signedness == .unsigned) { 1771 | return total_sum / @as(TItem, @intCast(total_count)); 1772 | } else { 1773 | @compileError("Signed integer type '" ++ @typeName(TItem) ++ "' should be used either with .averageTrunc() or .averageFloor()"); 1774 | } 1775 | } else { 1776 | @compileError("Iterators with item type '" ++ @typeName(TItem) ++ "' do not support .average()."); 1777 | } 1778 | } 1779 | 1780 | test average { 1781 | try std.testing.expectEqual(@as(f32, 2.5), sequence(f32, 1, 5).average()); 1782 | try std.testing.expectEqual(@as(u32, 2), sequence(u32, 1, 5).average()); 1783 | } 1784 | 1785 | /// Returns a truncated average value of all the numbers in the sequence. 1786 | /// 1787 | /// Negative values round towards 0. 1788 | pub inline fn averageTrunc(self: anytype) IteratorItem(@TypeOf(self)) { 1789 | const TItem = IteratorItem(@TypeOf(self)); 1790 | var self_copy = self.*; 1791 | var total_sum: TItem = 0; 1792 | var total_count: usize = 0; 1793 | while (self_copy.next()) |item| { 1794 | total_sum += item; 1795 | total_count += 1; 1796 | } 1797 | const item_type_info = @typeInfo(TItem); 1798 | if (item_type_info == .Int) { 1799 | return @divTrunc(total_sum, @as(TItem, @intCast(total_count))); 1800 | } else { 1801 | @compileError("Iterators with item type '" ++ @typeName(TItem) ++ "' do not support .averageTrunc()."); 1802 | } 1803 | } 1804 | 1805 | test averageTrunc { 1806 | try std.testing.expectEqual(@as(i32, -1), from(&[_]i32{ -1, -2, -2 }).averageTrunc()); 1807 | } 1808 | 1809 | /// Returns a floored average value of all the numbers in the sequence. 1810 | /// 1811 | /// Negative values round towards negative infinity. 1812 | pub inline fn averageFloor(self: anytype) IteratorItem(@TypeOf(self)) { 1813 | const TItem = IteratorItem(@TypeOf(self)); 1814 | var self_copy = self.*; 1815 | var total_sum: TItem = 0; 1816 | var total_count: usize = 0; 1817 | while (self_copy.next()) |item| { 1818 | total_sum += item; 1819 | total_count += 1; 1820 | } 1821 | const item_type_info = @typeInfo(TItem); 1822 | if (item_type_info == .Int) { 1823 | return @divFloor(total_sum, @as(TItem, @intCast(total_count))); 1824 | } else { 1825 | @compileError("Iterators with item type '" ++ @typeName(TItem) ++ "' do not support .averageFloor()."); 1826 | } 1827 | } 1828 | 1829 | test averageFloor { 1830 | try std.testing.expectEqual(@as(i32, -2), from(&[_]i32{ -1, -2, -2 }).averageFloor()); 1831 | } 1832 | 1833 | /// Asserts that the provided `actual_iter` is equal to the `expected_slice`. 1834 | inline fn expectEqualIter(expected_slice: anytype, actual_iter: anytype) !void { 1835 | const ItemType = IteratorItem(@TypeOf(actual_iter)); 1836 | var actual_array_list = std.ArrayList(ItemType).init(std.testing.allocator); 1837 | defer actual_array_list.deinit(); 1838 | const actual_iter_ptr = &actual_iter; 1839 | while (@constCast(actual_iter_ptr).next()) |item| { 1840 | try actual_array_list.append(item); 1841 | } 1842 | const actual_slice = actual_array_list.items; 1843 | try std.testing.expectEqualSlices(ItemType, expected_slice, actual_slice); 1844 | } 1845 | 1846 | test "example" { 1847 | try expectEqualIter( 1848 | "(1,2,3)", 1849 | from(std.mem.tokenizeAny(u8, "foo=1;bar=2;baz=3", "=;").buffer) 1850 | .where(std.ascii.isDigit) 1851 | .intersperse(',') 1852 | .prepend('(') 1853 | .append(')'), 1854 | ); 1855 | } 1856 | -------------------------------------------------------------------------------- /src/meta.zig: -------------------------------------------------------------------------------- 1 | pub fn ConstPtrWhenFn(comptime T: type) type { 2 | return switch (@typeInfo(T)) { 3 | .Fn => *const T, 4 | else => T, 5 | }; 6 | } 7 | 8 | pub inline fn CallableReturnType(comptime TCallable: type) ?type { 9 | return switch (@typeInfo(TCallable)) { 10 | .Fn => |info| info.return_type, 11 | .Pointer => |info| switch (@typeInfo(info.child)) { 12 | .Fn => |child_info| child_info.return_type, 13 | .Struct => ClosureReturnType(TCallable), 14 | else => @compileError("Unable to use '" ++ @typeName(TCallable) ++ "' as a function."), 15 | }, 16 | .Struct => ClosureReturnType(TCallable), 17 | else => @compileError("Unable to use '" ++ @typeName(TCallable) ++ "' as a function."), 18 | }; 19 | } 20 | 21 | pub inline fn ClosureReturnType(comptime TClosure: type) ?type { 22 | const type_info = @typeInfo(TClosure); 23 | if (type_info.Struct.decls.len != 1) { 24 | @compileError("Unable to use '" ++ @typeName(TClosure) ++ "' as a function. " ++ 25 | "To use struct as a closure make sure it has a single public function accepting " ++ 26 | "`@This()` and `TItem` as parameters."); 27 | } 28 | const decl_name = type_info.Struct.decls[0].name; 29 | const decl = @field(TClosure, decl_name); 30 | const decl_info = @typeInfo(@TypeOf(decl)); 31 | return decl_info.Fn.return_type; 32 | } 33 | 34 | pub inline fn callCallable(comptime TReturn: type, callable: anytype, arguments: anytype) TReturn { 35 | return switch (@typeInfo(@TypeOf(callable))) { 36 | .Fn => return @call(.auto, callable, arguments), 37 | .Pointer => |info| switch (@typeInfo(info.child)) { 38 | .Fn => return @call(.auto, callable, arguments), 39 | .Struct => callClosure(TReturn, callable.*, arguments), 40 | else => @compileError("Unable to use '" ++ @typeName(@TypeOf(callable)) ++ "' as a function."), 41 | }, 42 | .Struct => callClosure(TReturn, callable, arguments), 43 | else => @compileError("Unable to use '" ++ @typeName(@TypeOf(callable)) ++ "' as a function."), 44 | }; 45 | } 46 | 47 | pub inline fn callClosure( 48 | comptime TReturn: type, 49 | closure: anytype, 50 | arguments: anytype, 51 | ) TReturn { 52 | const type_info = @typeInfo(@TypeOf(closure)); 53 | if (type_info.Struct.decls.len != 1) { 54 | @compileError("Unable to use '" ++ @typeName(@TypeOf(closure)) ++ "' as a function. " ++ 55 | "To use struct as a closure make sure it has a single public function accepting " ++ 56 | "`@This()` and `TItem` as parameters."); 57 | } 58 | const function_name = type_info.Struct.decls[0].name; 59 | const field = @field(@TypeOf(closure), function_name); 60 | return @call(.auto, field, .{closure} ++ arguments); 61 | } 62 | -------------------------------------------------------------------------------- /zls.json: -------------------------------------------------------------------------------- 1 | { 2 | "enable_build_on_save": true, 3 | "build_on_save_step": "test" 4 | } 5 | --------------------------------------------------------------------------------