support packed structs in std.io.Reader and std.io.Writer

This commit is contained in:
kj4tmp@gmail.com 2024-10-04 22:14:38 -07:00 committed by Jeff Anderson
parent 0367f18cb9
commit ed612f40af
4 changed files with 151 additions and 17 deletions

View File

@ -324,20 +324,47 @@ pub fn isBytes(self: Self, slice: []const u8) anyerror!bool {
return matches;
}
/// Read a struct from the stream.
/// Only packed and extern structs are supported.
/// Packed structs must have a `@bitSizeOf` that is a multiple of eight.
pub fn readStruct(self: Self, comptime T: type) anyerror!T {
// Only extern and packed structs have defined in-memory layout.
comptime assert(@typeInfo(T).@"struct".layout != .auto);
var res: [1]T = undefined;
try self.readNoEof(mem.sliceAsBytes(res[0..]));
return res[0];
switch (@typeInfo(T).@"struct".layout) {
.auto => @compileError("readStruct only supports packed and extern structs."),
.@"extern" => {
var res: [1]T = undefined;
try self.readNoEof(mem.sliceAsBytes(res[0..]));
return res[0];
},
.@"packed" => {
var bytes: [@divExact(@bitSizeOf(T), 8)]u8 = undefined;
try self.readNoEof(&bytes);
return @bitCast(bytes);
},
}
}
/// Read a struct having the specified endianness into the host endianness representation.
/// Only packed and extern structs are supported.
/// Packed structs must have a `@bitSizeOf` that is a multiple of eight.
pub fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {
var res = try self.readStruct(T);
if (native_endian != endian) {
mem.byteSwapAllFields(T, &res);
switch (@typeInfo(T).@"struct".layout) {
.auto => @compileError("readStructEndian only supports packed and extern structs."),
.@"extern" => {
var res = try self.readStruct(T);
if (native_endian != endian) {
mem.byteSwapAllFields(T, &res);
}
return res;
},
.@"packed" => {
var bytes: [@divExact(@bitSizeOf(T), 8)]u8 = undefined;
try self.readNoEof(&bytes);
if (native_endian != endian) {
mem.reverse(u8, &bytes);
}
return @bitCast(bytes);
},
}
return res;
}
/// Reads an integer with the same size as the given enum's tag type. If the integer matches

View File

@ -1,6 +1,7 @@
const builtin = @import("builtin");
const std = @import("../../std.zig");
const testing = std.testing;
const native_endian = @import("builtin").target.cpu.arch.endian();
test "Reader" {
var buf = "a\x02".*;
@ -370,3 +371,44 @@ test "readIntoBoundedBytes correctly reads into a provided bounded array" {
try reader.readIntoBoundedBytes(10000, &bounded_array);
try testing.expectEqualStrings(bounded_array.slice(), test_string);
}
test "readStructEndian reads packed structs without padding and in correct field order" {
const buf = [3]u8{ 11, 12, 13 };
var fis = std.io.fixedBufferStream(&buf);
const reader = fis.reader();
const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 };
try testing.expectEqualDeep(
PackedStruct{ .a = 11, .b = 12, .c = 13 },
reader.readStructEndian(PackedStruct, .little),
);
fis.reset();
try testing.expectEqualDeep(
PackedStruct{ .a = 13, .b = 12, .c = 11 },
reader.readStructEndian(PackedStruct, .big),
);
}
test "readStruct reads packed structs without padding and in correct field order" {
const buf = [3]u8{ 11, 12, 13 };
var fis = std.io.fixedBufferStream(&buf);
const reader = fis.reader();
const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 };
switch (native_endian) {
.little => {
try testing.expectEqualDeep(
PackedStruct{ .a = 11, .b = 12, .c = 13 },
reader.readStruct(PackedStruct),
);
},
.big => {
try testing.expectEqualDeep(
PackedStruct{ .a = 13, .b = 12, .c = 11 },
reader.readStruct(PackedStruct),
);
},
}
}

View File

@ -54,20 +54,46 @@ pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.built
return self.writeAll(&bytes);
}
/// Write a struct to the stream.
/// Only packed and extern structs are supported.
/// Packed structs must have a `@bitSizeOf` that is a multiple of eight.
pub fn writeStruct(self: Self, value: anytype) anyerror!void {
// Only extern and packed structs have defined in-memory layout.
comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto);
return self.writeAll(mem.asBytes(&value));
switch (@typeInfo(@TypeOf(value)).@"struct".layout) {
.auto => @compileError("writeStruct only supports packed and extern structs."),
.@"extern" => {
return try self.writeAll(mem.asBytes(&value));
},
.@"packed" => {
const bytes: [@divExact(@bitSizeOf(@TypeOf(value)), 8)]u8 = @bitCast(value);
try self.writeAll(&bytes);
},
}
}
/// Write a struct to the stream in the specified endianness.
/// Only packed and extern structs are supported.
/// Packed structs must have a `@bitSizeOf` that is a multiple of eight.
pub fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) anyerror!void {
// TODO: make sure this value is not a reference type
if (native_endian == endian) {
return self.writeStruct(value);
} else {
var copy = value;
mem.byteSwapAllFields(@TypeOf(value), &copy);
return self.writeStruct(copy);
switch (@typeInfo(@TypeOf(value)).@"struct".layout) {
.auto => @compileError("writeStructEndian only supports packed and extern structs."),
.@"extern" => {
if (native_endian == endian) {
return try self.writeStruct(value);
} else {
var copy = value;
mem.byteSwapAllFields(@TypeOf(value), &copy);
return try self.writeStruct(copy);
}
},
.@"packed" => {
var bytes: [@divExact(@bitSizeOf(@TypeOf(value)), 8)]u8 = @bitCast(value);
if (native_endian != endian) {
mem.reverse(u8, &bytes);
}
return try self.writeAll(&bytes);
},
}
}
@ -81,3 +107,7 @@ pub fn writeFile(self: Self, file: std.fs.File) anyerror!void {
if (n < buf.len) return;
}
}
test {
_ = @import("Writer/test.zig");
}

View File

@ -0,0 +1,35 @@
const std = @import("../../std.zig");
const testing = std.testing;
const native_endian = @import("builtin").target.cpu.arch.endian();
test "writeStruct writes packed structs without padding" {
var buf: [3]u8 = undefined;
var fis = std.io.fixedBufferStream(&buf);
const writer = fis.writer();
const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 };
try writer.writeStruct(PackedStruct{ .a = 11, .b = 12, .c = 13 });
switch (native_endian) {
.little => {
try testing.expectEqualSlices(u8, &.{ 11, 12, 13 }, &buf);
},
.big => {
try testing.expectEqualSlices(u8, &.{ 13, 12, 11 }, &buf);
},
}
}
test "writeStructEndian writes packed structs without padding and in correct field order" {
var buf: [3]u8 = undefined;
var fis = std.io.fixedBufferStream(&buf);
const writer = fis.writer();
const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 };
try writer.writeStructEndian(PackedStruct{ .a = 11, .b = 12, .c = 13 }, .little);
try testing.expectEqualSlices(u8, &.{ 11, 12, 13 }, &buf);
fis.reset();
try writer.writeStructEndian(PackedStruct{ .a = 11, .b = 12, .c = 13 }, .big);
try testing.expectEqualSlices(u8, &.{ 13, 12, 11 }, &buf);
}