Skip to content

Commit f3725cb

Browse files
committed
Rewrite Table iterator to be more Zig canonical
Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
1 parent dca8f0a commit f3725cb

File tree

2 files changed

+84
-72
lines changed

2 files changed

+84
-72
lines changed

src/lua.zig

Lines changed: 74 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -634,14 +634,10 @@ pub const Lua = struct {
634634
return self.state().objLen(-1);
635635
}
636636

637-
/// Creates an iterator over table entries.
637+
/// Creates an iterator for this table.
638638
///
639-
/// Advances table iteration by taking the current entry and returning the next one.
640-
/// Automatically deallocates the passed entry's resources before returning.
641-
///
642-
/// To start iteration, pass `null` as the entry parameter.
643-
/// When iteration is complete, returns `null`. Note that the passed entry
644-
/// is always deallocated, even when iteration ends.
639+
/// The returned iterator automatically handles all resource management
640+
/// for safe iteration over table entries.
645641
///
646642
/// Examples:
647643
/// ```zig
@@ -651,59 +647,20 @@ pub const Lua = struct {
651647
/// try table.set("name", "Alice");
652648
/// try table.set(1, "first");
653649
///
654-
/// var current: ?Table.Entry = null;
655-
/// while (try table.next(current)) |entry| {
656-
/// current = entry;
657-
///
658-
/// // Use helper methods to access values
650+
/// var iterator = table.iterator();
651+
/// while (try iterator.next()) |entry| {
659652
/// if (entry.key.asString()) |s| {
660653
/// std.debug.print("String key: {s}\n", .{s});
661-
/// } else if (entry.key.asNumber()) |n| {
662-
/// std.debug.print("Number key: {d}\n", .{n});
663-
/// }
664-
///
665-
/// if (entry.value.asString()) |s| {
666-
/// std.debug.print("String value: {s}\n", .{s});
667-
/// } else if (entry.value.asInt()) |i| {
668-
/// std.debug.print("Int value: {d}\n", .{i});
669654
/// }
670655
/// }
671656
/// ```
672657
///
673-
/// Parameters:
674-
/// - `current_entry`: The current entry from previous iteration, or `null` to start
675-
///
676-
/// Returns: `?Entry` - The next key-value pair, or `null` if iteration is complete
677-
/// Errors: `Error.OutOfMemory` if stack allocation fails
678-
pub fn next(self: Table, current_entry: ?Entry) !?Entry {
679-
defer if (current_entry) |entry| entry.deinit();
680-
681-
try self.ref.lua.checkStack(3);
682-
683-
// Push table onto stack
684-
stack.push(self.ref.lua, self.ref);
685-
defer self.state().pop(1);
686-
687-
// Push key for lua_next (nil if null)
688-
if (current_entry) |entry| {
689-
stack.push(self.ref.lua, entry.key);
690-
} else {
691-
self.state().pushNil();
692-
}
693-
694-
// Call lua_next: pops key, pushes next key-value pair (or nothing if done)
695-
if (self.state().next(-2)) {
696-
// Stack now has: table, key, value
697-
698-
// Pop value and key
699-
const value = stack.pop(self.ref.lua, Lua.Value).?;
700-
const key = stack.pop(self.ref.lua, Lua.Value).?;
701-
702-
return Entry{ .key = key, .value = value };
703-
} else {
704-
// No more entries
705-
return null;
706-
}
658+
/// Returns: `Iterator` - A new iterator instance ready for use
659+
pub fn iterator(self: Table) Iterator {
660+
return Iterator{
661+
.table = self,
662+
.current_entry = null,
663+
};
707664
}
708665

709666
/// Set the readonly state of this table.
@@ -788,19 +745,79 @@ pub const Lua = struct {
788745
}
789746

790747
/// Entry representing a key-value pair from table iteration.
791-
/// Must be explicitly cleaned up using `deinit()` or passed to the next
792-
/// `next()` call for automatic deallocation.
748+
/// Resources are automatically managed when using the `Iterator` type.
793749
pub const Entry = struct {
794750
key: Lua.Value,
795751
value: Lua.Value,
796752

797753
/// Releases the resources held by this entry.
798-
/// Note: This is automatically called when passing the entry to `next()`.
754+
/// Note: This is automatically called by the Iterator.
799755
pub fn deinit(self: Entry) void {
800756
self.key.deinit();
801757
self.value.deinit();
802758
}
803759
};
760+
761+
/// Iterator for table entries.
762+
///
763+
/// The iterator handles all entry cleanup automatically.
764+
///
765+
/// Examples:
766+
/// ```zig
767+
/// const table = lua.createTable(.{});
768+
/// defer table.deinit();
769+
///
770+
/// try table.set("name", "Alice");
771+
/// try table.set(1, "first");
772+
///
773+
/// var iterator = table.iterator();
774+
/// while (try iterator.next()) |entry| {
775+
/// if (entry.key.asString()) |s| {
776+
/// std.debug.print("String key: {s}\n", .{s});
777+
/// }
778+
/// }
779+
/// ```
780+
pub const Iterator = struct {
781+
table: Table,
782+
current_entry: ?Entry,
783+
784+
/// Advances the iterator and returns the next entry, or null if done.
785+
/// Automatically manages cleanup of previous entries.
786+
///
787+
/// Returns: `?*const Entry` - Pointer to the next entry, or null if iteration complete
788+
/// Errors: `Error.OutOfMemory` if stack allocation fails
789+
pub fn next(self: *Iterator) !?*const Entry {
790+
try self.table.ref.lua.checkStack(3);
791+
792+
// Push table onto stack
793+
stack.push(self.table.ref.lua, self.table.ref);
794+
defer self.table.state().pop(1);
795+
796+
// Push key for lua_next (nil if null)
797+
if (self.current_entry) |entry| {
798+
stack.push(self.table.ref.lua, entry.key);
799+
entry.deinit();
800+
} else {
801+
self.table.state().pushNil();
802+
}
803+
804+
// Call lua_next: pops key, pushes next key-value pair (or nothing if done)
805+
if (self.table.state().next(-2)) {
806+
// Stack now has: table, key, value
807+
808+
// Pop value and key
809+
const value = stack.pop(self.table.ref.lua, Lua.Value).?;
810+
const key = stack.pop(self.table.ref.lua, Lua.Value).?;
811+
812+
self.current_entry = Entry{ .key = key, .value = value };
813+
return &self.current_entry.?;
814+
} else {
815+
// No more entries
816+
self.current_entry = null;
817+
return null;
818+
}
819+
}
820+
};
804821
};
805822

806823
/// Generic Lua value that can represent any runtime Lua type.

src/tests.zig

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -932,12 +932,10 @@ test "userdata with __index and __newindex metamethods" {
932932
try expectEq(lua.top(), 0);
933933
}
934934

935-
test "table iterator" {
935+
test "table canonical iterator" {
936936
const lua = try Lua.init(&std.testing.allocator);
937937
defer lua.deinit();
938938

939-
lua.openLibs();
940-
941939
const table = lua.createTable(.{});
942940
defer table.deinit();
943941

@@ -947,16 +945,15 @@ test "table iterator" {
947945
try table.set(1, "first");
948946
try table.set(2, "second");
949947

950-
// Test iteration using next() method
948+
// Test canonical Zig iterator pattern
951949
var count: i32 = 0;
952950
var found_name = false;
953951
var found_age = false;
954952
var found_first = false;
955953
var found_second = false;
956954

957-
var current: ?Lua.Table.Entry = null;
958-
while (try table.next(current)) |entry| {
959-
current = entry;
955+
var iterator = table.iterator();
956+
while (try iterator.next()) |entry| {
960957
count += 1;
961958

962959
// Check what we found using helper methods
@@ -971,20 +968,20 @@ test "table iterator" {
971968
} else if (std.mem.eql(u8, s, "age")) {
972969
found_age = true;
973970
if (entry.value.asNumber()) |v| {
974-
try expectEq(v, 30.0);
971+
try expectEq(v, 30);
975972
} else {
976973
try expect(false);
977974
}
978975
}
979976
} else if (entry.key.asNumber()) |n| {
980-
if (n == 1.0) {
977+
if (n == 1) {
981978
found_first = true;
982979
if (entry.value.asString()) |v| {
983980
try expect(std.mem.eql(u8, v, "first"));
984981
} else {
985982
try expect(false);
986983
}
987-
} else if (n == 2.0) {
984+
} else if (n == 2) {
988985
found_second = true;
989986
if (entry.value.asString()) |v| {
990987
try expect(std.mem.eql(u8, v, "second"));
@@ -995,21 +992,19 @@ test "table iterator" {
995992
}
996993
}
997994

998-
// Verify we found all entries
999995
try expectEq(count, 4);
1000996
try expect(found_name);
1001997
try expect(found_age);
1002998
try expect(found_first);
1003999
try expect(found_second);
10041000

1005-
// Test iteration on empty table
1001+
// Test iterator with empty table
10061002
const empty_table = lua.createTable(.{});
10071003
defer empty_table.deinit();
10081004

1005+
var empty_iterator = empty_table.iterator();
10091006
var empty_count: i32 = 0;
1010-
var empty_current: ?Lua.Table.Entry = null;
1011-
while (try empty_table.next(empty_current)) |entry| {
1012-
empty_current = entry;
1007+
while (try empty_iterator.next()) |_| {
10131008
empty_count += 1;
10141009
}
10151010
try expectEq(empty_count, 0);

0 commit comments

Comments
 (0)