authorgravatar for motiejus@jakstys.ltMotiejus Jakštys <motiejus@jakstys.lt> 2022-06-29 09:23:09+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-06-29 08:23:09+02:00
log4a6b70fbd1e8f6de25ae00c16051aa9613d8a7bc
tree916ec90566abfdaafa11d50b33ad0b2ab3a34a6d
parentbb3e1bcf31bdf9306030e797ed673021c7766d58
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

mem: add splitBackwards (#11908)

* mem: refactor tests of split() - add a few cases for .rest() - use expectEqualSlices() * mem: add splitBackwards Over the last couple of weeks weeks I needed to iterate over a collection backwards at least twice. Do we want to have this in stdlib? If yes, click "Merge" and start using today! Free shipping and returns (before 1.0). Why is this useful? ------------------- I need this for building an error wrapper: errors are added in the wrapper from "lowest" level to "highest" level, and then printed in reverse order. Imagine `UpdateUsers` call, which needs to return `error.InvalidInput` and a wrappable error context. In Go we would add a context to the error when returning it: // if update_user fails, add context on which user we are operating if err := update_user(user); err != nil { return fmt.Errorf("user id=%d: %w", user.id, err) } Since Zig cannot pass anything else than u16 with an error (#2647), I will pass a `err_ctx: *Err`, to the callers, where they can, besides returning an error, augment it with auxiliary data. `Err` is a preallocated array that can add zero-byte-separated strings. For a concrete example, imagine such a call graph: update_user(User, *Err) error{InvalidInput}!<...> validate_user([]const u8, *Err) error{InvalidInput}!<...> Where `validate_user` would like, besides only the error, signal the invalid field. And `update_user`, besides the error, would signal the offending user id. We also don't want the low-level functions to know in which context they are operating to construct a meaningful error message: if validation fails, they append their "context" to the buffer. To translate/augment the Go example above: pub fn validate_user(err_ctx: *Err, user: User) error{InvalidInput}!void { const name = user.name; if (!ascii.isAlpha(name)) { err_ctx.print("name '{s}' must be ascii-letters only", .{name}); return error.InvalidInput; } <...> } // update_user validates each user and does something with it. pub fn update_user(err_ctx: *Err, user: User) error{InvalidInput}!void { // validate the user before updating it validate_user(user) catch { err_ctx.print("user id={d}", .{user.id}); return error.InvalidInput; }; <...> } Then the top-level function (in my case, CLI) will read the buffer backwards (splitting on `"\x00"`) and print: user id=123: name 'Žvangalas' must be ascii-letters only To read that buffer backwards, dear readers of this commit message, I need `mem.splitBackwards`.

1 files changed, 146 insertions(+), 19 deletions(-)

lib/std/mem.zig+146-19
......@@ -1707,23 +1707,32 @@ pub fn split(comptime T: type, buffer: []const T, delimiter: []const T) SplitIte
17071707
17081708test "split" {
17091709 var it = split(u8, "abc|def||ghi", "|");
1710 try testing.expect(eql(u8, it.next().?, "abc"));
1711 try testing.expect(eql(u8, it.next().?, "def"));
1712 try testing.expect(eql(u8, it.next().?, ""));
1713 try testing.expect(eql(u8, it.next().?, "ghi"));
1710 try testing.expectEqualSlices(u8, it.rest(), "abc|def||ghi");
1711 try testing.expectEqualSlices(u8, it.next().?, "abc");
1712
1713 try testing.expectEqualSlices(u8, it.rest(), "def||ghi");
1714 try testing.expectEqualSlices(u8, it.next().?, "def");
1715
1716 try testing.expectEqualSlices(u8, it.rest(), "|ghi");
1717 try testing.expectEqualSlices(u8, it.next().?, "");
1718
1719 try testing.expectEqualSlices(u8, it.rest(), "ghi");
1720 try testing.expectEqualSlices(u8, it.next().?, "ghi");
1721
1722 try testing.expectEqualSlices(u8, it.rest(), "");
17141723 try testing.expect(it.next() == null);
17151724
17161725 it = split(u8, "", "|");
1717 try testing.expect(eql(u8, it.next().?, ""));
1726 try testing.expectEqualSlices(u8, it.next().?, "");
17181727 try testing.expect(it.next() == null);
17191728
17201729 it = split(u8, "|", "|");
1721 try testing.expect(eql(u8, it.next().?, ""));
1722 try testing.expect(eql(u8, it.next().?, ""));
1730 try testing.expectEqualSlices(u8, it.next().?, "");
1731 try testing.expectEqualSlices(u8, it.next().?, "");
17231732 try testing.expect(it.next() == null);
17241733
17251734 it = split(u8, "hello", " ");
1726 try testing.expect(eql(u8, it.next().?, "hello"));
1735 try testing.expectEqualSlices(u8, it.next().?, "hello");
17271736 try testing.expect(it.next() == null);
17281737
17291738 var it16 = split(
......@@ -1731,17 +1740,18 @@ test "split" {
17311740 std.unicode.utf8ToUtf16LeStringLiteral("hello"),
17321741 std.unicode.utf8ToUtf16LeStringLiteral(" "),
17331742 );
1734 try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("hello")));
1743 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("hello"));
17351744 try testing.expect(it16.next() == null);
17361745}
17371746
17381747test "split (multibyte)" {
17391748 var it = split(u8, "a, b ,, c, d, e", ", ");
1740 try testing.expect(eql(u8, it.next().?, "a"));
1741 try testing.expect(eql(u8, it.next().?, "b ,"));
1742 try testing.expect(eql(u8, it.next().?, "c"));
1743 try testing.expect(eql(u8, it.next().?, "d"));
1744 try testing.expect(eql(u8, it.next().?, "e"));
1749 try testing.expectEqualSlices(u8, it.next().?, "a");
1750 try testing.expectEqualSlices(u8, it.rest(), "b ,, c, d, e");
1751 try testing.expectEqualSlices(u8, it.next().?, "b ,");
1752 try testing.expectEqualSlices(u8, it.next().?, "c");
1753 try testing.expectEqualSlices(u8, it.next().?, "d");
1754 try testing.expectEqualSlices(u8, it.next().?, "e");
17451755 try testing.expect(it.next() == null);
17461756
17471757 var it16 = split(
......@@ -1749,11 +1759,99 @@ test "split (multibyte)" {
17491759 std.unicode.utf8ToUtf16LeStringLiteral("a, b ,, c, d, e"),
17501760 std.unicode.utf8ToUtf16LeStringLiteral(", "),
17511761 );
1752 try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("a")));
1753 try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("b ,")));
1754 try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("c")));
1755 try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("d")));
1756 try testing.expect(eql(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("e")));
1762 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("a"));
1763 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("b ,"));
1764 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("c"));
1765 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("d"));
1766 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("e"));
1767 try testing.expect(it16.next() == null);
1768}
1769
1770/// Returns an iterator that iterates backwards over the slices of `buffer`
1771/// that are separated by bytes in `delimiter`.
1772/// splitBackwards(u8, "abc|def||ghi", "|")
1773/// will return slices for "ghi", "", "def", "abc", null, in that order.
1774/// If `delimiter` does not exist in buffer,
1775/// the iterator will return `buffer`, null, in that order.
1776/// The delimiter length must not be zero.
1777pub fn splitBackwards(comptime T: type, buffer: []const T, delimiter: []const T) SplitBackwardsIterator(T) {
1778 assert(delimiter.len != 0);
1779 return SplitBackwardsIterator(T){
1780 .index = buffer.len,
1781 .buffer = buffer,
1782 .delimiter = delimiter,
1783 };
1784}
1785
1786test "splitBackwards" {
1787 var it = splitBackwards(u8, "abc|def||ghi", "|");
1788 try testing.expectEqualSlices(u8, it.rest(), "abc|def||ghi");
1789 try testing.expectEqualSlices(u8, it.next().?, "ghi");
1790
1791 try testing.expectEqualSlices(u8, it.rest(), "abc|def|");
1792 try testing.expectEqualSlices(u8, it.next().?, "");
1793
1794 try testing.expectEqualSlices(u8, it.rest(), "abc|def");
1795 try testing.expectEqualSlices(u8, it.next().?, "def");
1796
1797 try testing.expectEqualSlices(u8, it.rest(), "abc");
1798 try testing.expectEqualSlices(u8, it.next().?, "abc");
1799
1800 try testing.expectEqualSlices(u8, it.rest(), "");
1801 try testing.expect(it.next() == null);
1802
1803 it = splitBackwards(u8, "", "|");
1804 try testing.expectEqualSlices(u8, it.next().?, "");
1805 try testing.expect(it.next() == null);
1806
1807 it = splitBackwards(u8, "|", "|");
1808 try testing.expectEqualSlices(u8, it.next().?, "");
1809 try testing.expectEqualSlices(u8, it.next().?, "");
1810 try testing.expect(it.next() == null);
1811
1812 it = splitBackwards(u8, "hello", " ");
1813 try testing.expectEqualSlices(u8, it.next().?, "hello");
1814 try testing.expect(it.next() == null);
1815
1816 var it16 = splitBackwards(
1817 u16,
1818 std.unicode.utf8ToUtf16LeStringLiteral("hello"),
1819 std.unicode.utf8ToUtf16LeStringLiteral(" "),
1820 );
1821 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("hello"));
1822 try testing.expect(it16.next() == null);
1823}
1824
1825test "splitBackwards (multibyte)" {
1826 var it = splitBackwards(u8, "a, b ,, c, d, e", ", ");
1827 try testing.expectEqualSlices(u8, it.rest(), "a, b ,, c, d, e");
1828 try testing.expectEqualSlices(u8, it.next().?, "e");
1829
1830 try testing.expectEqualSlices(u8, it.rest(), "a, b ,, c, d");
1831 try testing.expectEqualSlices(u8, it.next().?, "d");
1832
1833 try testing.expectEqualSlices(u8, it.rest(), "a, b ,, c");
1834 try testing.expectEqualSlices(u8, it.next().?, "c");
1835
1836 try testing.expectEqualSlices(u8, it.rest(), "a, b ,");
1837 try testing.expectEqualSlices(u8, it.next().?, "b ,");
1838
1839 try testing.expectEqualSlices(u8, it.rest(), "a");
1840 try testing.expectEqualSlices(u8, it.next().?, "a");
1841
1842 try testing.expectEqualSlices(u8, it.rest(), "");
1843 try testing.expect(it.next() == null);
1844
1845 var it16 = splitBackwards(
1846 u16,
1847 std.unicode.utf8ToUtf16LeStringLiteral("a, b ,, c, d, e"),
1848 std.unicode.utf8ToUtf16LeStringLiteral(", "),
1849 );
1850 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("e"));
1851 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("d"));
1852 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("c"));
1853 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("b ,"));
1854 try testing.expectEqualSlices(u16, it16.next().?, std.unicode.utf8ToUtf16LeStringLiteral("a"));
17571855 try testing.expect(it16.next() == null);
17581856}
17591857
......@@ -1862,6 +1960,35 @@ pub fn SplitIterator(comptime T: type) type {
18621960 };
18631961}
18641962
1963pub fn SplitBackwardsIterator(comptime T: type) type {
1964 return struct {
1965 buffer: []const T,
1966 index: ?usize,
1967 delimiter: []const T,
1968
1969 const Self = @This();
1970
1971 /// Returns a slice of the next field, or null if splitting is complete.
1972 pub fn next(self: *Self) ?[]const T {
1973 const end = self.index orelse return null;
1974 const start = if (lastIndexOf(T, self.buffer[0..end], self.delimiter)) |delim_start| blk: {
1975 self.index = delim_start;
1976 break :blk delim_start + self.delimiter.len;
1977 } else blk: {
1978 self.index = null;
1979 break :blk 0;
1980 };
1981 return self.buffer[start..end];
1982 }
1983
1984 /// Returns a slice of the remaining bytes. Does not affect iterator state.
1985 pub fn rest(self: Self) []const T {
1986 const end = self.index orelse 0;
1987 return self.buffer[0..end];
1988 }
1989 };
1990}
1991
18651992/// Naively combines a series of slices with a separator.
18661993/// Allocates memory for the result, which must be freed by the caller.
18671994pub fn join(allocator: Allocator, separator: []const u8, slices: []const []const u8) ![]u8 {