diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 046339b6fcc7..f54610f06091 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -1707,23 +1707,32 @@ pub fn split(comptime T: type, buffer: []const T, delimiter: []const T) SplitIte test "split" { var it = split(u8, "abc|def||ghi", "|"); - try testing.expect(eql(u8, it.next().?, "abc")); - try testing.expect(eql(u8, it.next().?, "def")); - try testing.expect(eql(u8, it.next().?, "")); - try testing.expect(eql(u8, it.next().?, "ghi")); + try testing.expectEqualSlices(u8, it.rest(), "abc|def||ghi"); + try testing.expectEqualSlices(u8, it.next().?, "abc"); + + try testing.expectEqualSlices(u8, it.rest(), "def||ghi"); + try testing.expectEqualSlices(u8, it.next().?, "def"); + + try testing.expectEqualSlices(u8, it.rest(), "|ghi"); + try testing.expectEqualSlices(u8, it.next().?, ""); + + try testing.expectEqualSlices(u8, it.rest(), "ghi"); + try testing.expectEqualSlices(u8, it.next().?, "ghi"); + + try testing.expectEqualSlices(u8, it.rest(), ""); try testing.expect(it.next() == null); it = split(u8, "", "|"); - try testing.expect(eql(u8, it.next().?, "")); + try testing.expectEqualSlices(u8, it.next().?, ""); try testing.expect(it.next() == null); it = split(u8, "|", "|"); - try testing.expect(eql(u8, it.next().?, "")); - try testing.expect(eql(u8, it.next().?, "")); + try testing.expectEqualSlices(u8, it.next().?, ""); + try testing.expectEqualSlices(u8, it.next().?, ""); try testing.expect(it.next() == null); it = split(u8, "hello", " "); - try testing.expect(eql(u8, it.next().?, "hello")); + try testing.expectEqualSlices(u8, it.next().?, "hello"); try testing.expect(it.next() == null); var it16 = split( @@ -1731,17 +1740,18 @@ test "split" { std.unicode.utf8ToUtf16LeStringLiteral("hello"), std.unicode.utf8ToUtf16LeStringLiteral(" "), ); - try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("hello"))); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("hello")); try testing.expect(it16.next() == null); } test "split (multibyte)" { var it = split(u8, "a, b ,, c, d, e", ", "); - try testing.expect(eql(u8, it.next().?, "a")); - try testing.expect(eql(u8, it.next().?, "b ,")); - try testing.expect(eql(u8, it.next().?, "c")); - try testing.expect(eql(u8, it.next().?, "d")); - try testing.expect(eql(u8, it.next().?, "e")); + try testing.expectEqualSlices(u8, it.next().?, "a"); + try testing.expectEqualSlices(u8, it.rest(), "b ,, c, d, e"); + try testing.expectEqualSlices(u8, it.next().?, "b ,"); + try testing.expectEqualSlices(u8, it.next().?, "c"); + try testing.expectEqualSlices(u8, it.next().?, "d"); + try testing.expectEqualSlices(u8, it.next().?, "e"); try testing.expect(it.next() == null); var it16 = split( @@ -1749,11 +1759,99 @@ test "split (multibyte)" { std.unicode.utf8ToUtf16LeStringLiteral("a, b ,, c, d, e"), std.unicode.utf8ToUtf16LeStringLiteral(", "), ); - try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("a"))); - try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("b ,"))); - try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("c"))); - try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("d"))); - try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("e"))); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("a")); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("b ,")); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("c")); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("d")); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("e")); + try testing.expect(it16.next() == null); +} + +/// Returns an iterator that iterates backwards over the slices of `buffer` +/// that are separated by bytes in `delimiter`. +/// splitBackwards(u8, "abc|def||ghi", "|") +/// will return slices for "ghi", "", "def", "abc", null, in that order. +/// If `delimiter` does not exist in buffer, +/// the iterator will return `buffer`, null, in that order. +/// The delimiter length must not be zero. +pub fn splitBackwards(comptime T: type, buffer: []const T, delimiter: []const T) SplitBackwardsIterator(T) { + assert(delimiter.len != 0); + return SplitBackwardsIterator(T){ + .index = buffer.len, + .buffer = buffer, + .delimiter = delimiter, + }; +} + +test "splitBackwards" { + var it = splitBackwards(u8, "abc|def||ghi", "|"); + try testing.expectEqualSlices(u8, it.rest(), "abc|def||ghi"); + try testing.expectEqualSlices(u8, it.next().?, "ghi"); + + try testing.expectEqualSlices(u8, it.rest(), "abc|def|"); + try testing.expectEqualSlices(u8, it.next().?, ""); + + try testing.expectEqualSlices(u8, it.rest(), "abc|def"); + try testing.expectEqualSlices(u8, it.next().?, "def"); + + try testing.expectEqualSlices(u8, it.rest(), "abc"); + try testing.expectEqualSlices(u8, it.next().?, "abc"); + + try testing.expectEqualSlices(u8, it.rest(), ""); + try testing.expect(it.next() == null); + + it = splitBackwards(u8, "", "|"); + try testing.expectEqualSlices(u8, it.next().?, ""); + try testing.expect(it.next() == null); + + it = splitBackwards(u8, "|", "|"); + try testing.expectEqualSlices(u8, it.next().?, ""); + try testing.expectEqualSlices(u8, it.next().?, ""); + try testing.expect(it.next() == null); + + it = splitBackwards(u8, "hello", " "); + try testing.expectEqualSlices(u8, it.next().?, "hello"); + try testing.expect(it.next() == null); + + var it16 = splitBackwards( + u16, + std.unicode.utf8ToUtf16LeStringLiteral("hello"), + std.unicode.utf8ToUtf16LeStringLiteral(" "), + ); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("hello")); + try testing.expect(it16.next() == null); +} + +test "splitBackwards (multibyte)" { + var it = splitBackwards(u8, "a, b ,, c, d, e", ", "); + try testing.expectEqualSlices(u8, it.rest(), "a, b ,, c, d, e"); + try testing.expectEqualSlices(u8, it.next().?, "e"); + + try testing.expectEqualSlices(u8, it.rest(), "a, b ,, c, d"); + try testing.expectEqualSlices(u8, it.next().?, "d"); + + try testing.expectEqualSlices(u8, it.rest(), "a, b ,, c"); + try testing.expectEqualSlices(u8, it.next().?, "c"); + + try testing.expectEqualSlices(u8, it.rest(), "a, b ,"); + try testing.expectEqualSlices(u8, it.next().?, "b ,"); + + try testing.expectEqualSlices(u8, it.rest(), "a"); + try testing.expectEqualSlices(u8, it.next().?, "a"); + + try testing.expectEqualSlices(u8, it.rest(), ""); + try testing.expect(it.next() == null); + + var it16 = splitBackwards( + u16, + std.unicode.utf8ToUtf16LeStringLiteral("a, b ,, c, d, e"), + std.unicode.utf8ToUtf16LeStringLiteral(", "), + ); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("e")); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("d")); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("c")); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("b ,")); + try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("a")); try testing.expect(it16.next() == null); } @@ -1862,6 +1960,35 @@ pub fn SplitIterator(comptime T: type) type { }; } +pub fn SplitBackwardsIterator(comptime T: type) type { + return struct { + buffer: []const T, + index: ?usize, + delimiter: []const T, + + const Self = @This(); + + /// Returns a slice of the next field, or null if splitting is complete. + pub fn next(self: *Self) ?[]const T { + const end = self.index orelse return null; + const start = if (lastIndexOf(T, self.buffer[0..end], self.delimiter)) |delim_start| blk: { + self.index = delim_start; + break :blk delim_start + self.delimiter.len; + } else blk: { + self.index = null; + break :blk 0; + }; + return self.buffer[start..end]; + } + + /// Returns a slice of the remaining bytes. Does not affect iterator state. + pub fn rest(self: Self) []const T { + const end = self.index orelse 0; + return self.buffer[0..end]; + } + }; +} + /// Naively combines a series of slices with a separator. /// Allocates memory for the result, which must be freed by the caller. pub fn join(allocator: Allocator, separator: []const u8, slices: []const []const u8) ![]u8 {