Skip to content

Commit 43da102

Browse files
authored
Merge pull request #8698 from ifreund/scanZ
std/mem: add sliceTo(), deprecate spanZ(), lenZ()
2 parents 893a428 + cd7b5a3 commit 43da102

File tree

2 files changed

+189
-7
lines changed

2 files changed

+189
-7
lines changed

lib/std/mem.zig

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@ test "span" {
602602
try testing.expectEqual(@as(?[:0]u16, null), span(@as(?[*:0]u16, null)));
603603
}
604604

605+
/// Deprecated: use std.mem.span() or std.mem.sliceTo()
605606
/// Same as `span`, except when there is both a sentinel and an array
606607
/// length or slice length, scans the memory for the sentinel value
607608
/// rather than using the length.
@@ -630,6 +631,192 @@ test "spanZ" {
630631
try testing.expectEqual(@as(?[:0]u16, null), spanZ(@as(?[*:0]u16, null)));
631632
}
632633

634+
/// Helper for the return type of sliceTo()
635+
fn SliceTo(comptime T: type, comptime end: meta.Elem(T)) type {
636+
switch (@typeInfo(T)) {
637+
.Optional => |optional_info| {
638+
return ?SliceTo(optional_info.child, end);
639+
},
640+
.Pointer => |ptr_info| {
641+
var new_ptr_info = ptr_info;
642+
new_ptr_info.size = .Slice;
643+
switch (ptr_info.size) {
644+
.One => switch (@typeInfo(ptr_info.child)) {
645+
.Array => |array_info| {
646+
new_ptr_info.child = array_info.child;
647+
// The return type must only be sentinel terminated if we are guaranteed
648+
// to find the value searched for, which is only the case if it matches
649+
// the sentinel of the type passed.
650+
if (array_info.sentinel) |sentinel| {
651+
if (end == sentinel) {
652+
new_ptr_info.sentinel = end;
653+
} else {
654+
new_ptr_info.sentinel = null;
655+
}
656+
}
657+
},
658+
else => {},
659+
},
660+
.Many, .Slice => {
661+
// The return type must only be sentinel terminated if we are guaranteed
662+
// to find the value searched for, which is only the case if it matches
663+
// the sentinel of the type passed.
664+
if (ptr_info.sentinel) |sentinel| {
665+
if (end == sentinel) {
666+
new_ptr_info.sentinel = end;
667+
} else {
668+
new_ptr_info.sentinel = null;
669+
}
670+
}
671+
},
672+
.C => {
673+
new_ptr_info.sentinel = end;
674+
// C pointers are always allowzero, but we don't want the return type to be.
675+
assert(new_ptr_info.is_allowzero);
676+
new_ptr_info.is_allowzero = false;
677+
},
678+
}
679+
return @Type(std.builtin.TypeInfo{ .Pointer = new_ptr_info });
680+
},
681+
else => {},
682+
}
683+
@compileError("invalid type given to std.mem.sliceTo: " ++ @typeName(T));
684+
}
685+
686+
/// Takes a pointer to an array, an array, a sentinel-terminated pointer, or a slice and
687+
/// iterates searching for the first occurrence of `end`, returning the scanned slice.
688+
/// If `end` is not found, the full length of the array/slice/sentinel terminated pointer is returned.
689+
/// If the pointer type is sentinel terminated and `end` matches that terminator, the
690+
/// resulting slice is also sentinel terminated.
691+
/// Pointer properties such as mutability and alignment are preserved.
692+
/// C pointers are assumed to be non-null.
693+
pub fn sliceTo(ptr: anytype, comptime end: meta.Elem(@TypeOf(ptr))) SliceTo(@TypeOf(ptr), end) {
694+
if (@typeInfo(@TypeOf(ptr)) == .Optional) {
695+
const non_null = ptr orelse return null;
696+
return sliceTo(non_null, end);
697+
}
698+
const Result = SliceTo(@TypeOf(ptr), end);
699+
const length = lenSliceTo(ptr, end);
700+
if (@typeInfo(Result).Pointer.sentinel) |s| {
701+
return ptr[0..length :s];
702+
} else {
703+
return ptr[0..length];
704+
}
705+
}
706+
707+
test "sliceTo" {
708+
try testing.expectEqualSlices(u8, "aoeu", sliceTo("aoeu", 0));
709+
710+
{
711+
var array: [5]u16 = [_]u16{ 1, 2, 3, 4, 5 };
712+
try testing.expectEqualSlices(u16, &array, sliceTo(&array, 0));
713+
try testing.expectEqualSlices(u16, array[0..3], sliceTo(array[0..3], 0));
714+
try testing.expectEqualSlices(u16, array[0..2], sliceTo(&array, 3));
715+
try testing.expectEqualSlices(u16, array[0..2], sliceTo(array[0..3], 3));
716+
717+
const sentinel_ptr = @ptrCast([*:5]u16, &array);
718+
try testing.expectEqualSlices(u16, array[0..2], sliceTo(sentinel_ptr, 3));
719+
try testing.expectEqualSlices(u16, array[0..4], sliceTo(sentinel_ptr, 99));
720+
721+
const optional_sentinel_ptr = @ptrCast(?[*:5]u16, &array);
722+
try testing.expectEqualSlices(u16, array[0..2], sliceTo(optional_sentinel_ptr, 3).?);
723+
try testing.expectEqualSlices(u16, array[0..4], sliceTo(optional_sentinel_ptr, 99).?);
724+
725+
const c_ptr = @as([*c]u16, &array);
726+
try testing.expectEqualSlices(u16, array[0..2], sliceTo(c_ptr, 3));
727+
728+
const slice: []u16 = &array;
729+
try testing.expectEqualSlices(u16, array[0..2], sliceTo(slice, 3));
730+
try testing.expectEqualSlices(u16, &array, sliceTo(slice, 99));
731+
732+
const sentinel_slice: [:5]u16 = array[0..4 :5];
733+
try testing.expectEqualSlices(u16, array[0..2], sliceTo(sentinel_slice, 3));
734+
try testing.expectEqualSlices(u16, array[0..4], sliceTo(sentinel_slice, 99));
735+
}
736+
{
737+
var sentinel_array: [5:0]u16 = [_:0]u16{ 1, 2, 3, 4, 5 };
738+
try testing.expectEqualSlices(u16, sentinel_array[0..2], sliceTo(&sentinel_array, 3));
739+
try testing.expectEqualSlices(u16, &sentinel_array, sliceTo(&sentinel_array, 0));
740+
try testing.expectEqualSlices(u16, &sentinel_array, sliceTo(&sentinel_array, 99));
741+
}
742+
743+
try testing.expectEqual(@as(?[]u8, null), sliceTo(@as(?[]u8, null), 0));
744+
}
745+
746+
/// Private helper for sliceTo(). If you want the length, use sliceTo(foo, x).len
747+
fn lenSliceTo(ptr: anytype, comptime end: meta.Elem(@TypeOf(ptr))) usize {
748+
switch (@typeInfo(@TypeOf(ptr))) {
749+
.Pointer => |ptr_info| switch (ptr_info.size) {
750+
.One => switch (@typeInfo(ptr_info.child)) {
751+
.Array => |array_info| {
752+
if (array_info.sentinel) |sentinel| {
753+
if (sentinel == end) {
754+
return indexOfSentinel(array_info.child, end, ptr);
755+
}
756+
}
757+
return indexOfScalar(array_info.child, ptr, end) orelse array_info.len;
758+
},
759+
else => {},
760+
},
761+
.Many => if (ptr_info.sentinel) |sentinel| {
762+
// We may be looking for something other than the sentinel,
763+
// but iterating past the sentinel would be a bug so we need
764+
// to check for both.
765+
var i: usize = 0;
766+
while (ptr[i] != end and ptr[i] != sentinel) i += 1;
767+
return i;
768+
},
769+
.C => {
770+
assert(ptr != null);
771+
return indexOfSentinel(ptr_info.child, end, ptr);
772+
},
773+
.Slice => {
774+
if (ptr_info.sentinel) |sentinel| {
775+
if (sentinel == end) {
776+
return indexOfSentinel(ptr_info.child, sentinel, ptr);
777+
}
778+
}
779+
return indexOfScalar(ptr_info.child, ptr, end) orelse ptr.len;
780+
},
781+
},
782+
else => {},
783+
}
784+
@compileError("invalid type given to std.mem.sliceTo: " ++ @typeName(@TypeOf(ptr)));
785+
}
786+
787+
test "lenSliceTo" {
788+
try testing.expect(lenSliceTo("aoeu", 0) == 4);
789+
790+
{
791+
var array: [5]u16 = [_]u16{ 1, 2, 3, 4, 5 };
792+
try testing.expectEqual(@as(usize, 5), lenSliceTo(&array, 0));
793+
try testing.expectEqual(@as(usize, 3), lenSliceTo(array[0..3], 0));
794+
try testing.expectEqual(@as(usize, 2), lenSliceTo(&array, 3));
795+
try testing.expectEqual(@as(usize, 2), lenSliceTo(array[0..3], 3));
796+
797+
const sentinel_ptr = @ptrCast([*:5]u16, &array);
798+
try testing.expectEqual(@as(usize, 2), lenSliceTo(sentinel_ptr, 3));
799+
try testing.expectEqual(@as(usize, 4), lenSliceTo(sentinel_ptr, 99));
800+
801+
const c_ptr = @as([*c]u16, &array);
802+
try testing.expectEqual(@as(usize, 2), lenSliceTo(c_ptr, 3));
803+
804+
const slice: []u16 = &array;
805+
try testing.expectEqual(@as(usize, 2), lenSliceTo(slice, 3));
806+
try testing.expectEqual(@as(usize, 5), lenSliceTo(slice, 99));
807+
808+
const sentinel_slice: [:5]u16 = array[0..4 :5];
809+
try testing.expectEqual(@as(usize, 2), lenSliceTo(sentinel_slice, 3));
810+
try testing.expectEqual(@as(usize, 4), lenSliceTo(sentinel_slice, 99));
811+
}
812+
{
813+
var sentinel_array: [5:0]u16 = [_:0]u16{ 1, 2, 3, 4, 5 };
814+
try testing.expectEqual(@as(usize, 2), lenSliceTo(&sentinel_array, 3));
815+
try testing.expectEqual(@as(usize, 5), lenSliceTo(&sentinel_array, 0));
816+
try testing.expectEqual(@as(usize, 5), lenSliceTo(&sentinel_array, 99));
817+
}
818+
}
819+
633820
/// Takes a pointer to an array, an array, a vector, a sentinel-terminated pointer,
634821
/// a slice or a tuple, and returns the length.
635822
/// In the case of a sentinel-terminated array, it uses the array length.
@@ -688,6 +875,7 @@ test "len" {
688875
}
689876
}
690877

878+
/// Deprecated: use std.mem.len() or std.mem.sliceTo().len
691879
/// Takes a pointer to an array, an array, a sentinel-terminated pointer,
692880
/// or a slice, and returns the length.
693881
/// In the case of a sentinel-terminated array, it scans the array

lib/std/meta.zig

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,7 @@ pub fn Elem(comptime T: type) type {
175175
},
176176
.Many, .C, .Slice => return info.child,
177177
},
178-
.Optional => |info| switch (@typeInfo(info.child)) {
179-
.Pointer => |ptr_info| switch (ptr_info.size) {
180-
.Many => return ptr_info.child,
181-
else => {},
182-
},
183-
else => {},
184-
},
178+
.Optional => |info| return Elem(info.child),
185179
else => {},
186180
}
187181
@compileError("Expected pointer, slice, array or vector type, found '" ++ @typeName(T) ++ "'");

0 commit comments

Comments
 (0)