diff --git a/CMakeLists.txt b/CMakeLists.txt index 28c2dcc8e8..1f3337589c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,7 +265,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/io/find_byte_writer.zig" "${CMAKE_SOURCE_DIR}/lib/std/io/fixed_buffer_stream.zig" "${CMAKE_SOURCE_DIR}/lib/std/io/limited_reader.zig" - "${CMAKE_SOURCE_DIR}/lib/std/io/reader.zig" + "${CMAKE_SOURCE_DIR}/lib/std/io/Reader.zig" "${CMAKE_SOURCE_DIR}/lib/std/io/seekable_stream.zig" "${CMAKE_SOURCE_DIR}/lib/std/io/writer.zig" "${CMAKE_SOURCE_DIR}/lib/std/json.zig" diff --git a/lib/std/Build/Step/CheckObject.zig b/lib/std/Build/Step/CheckObject.zig index 26d3e89552..2534298854 100644 --- a/lib/std/Build/Step/CheckObject.zig +++ b/lib/std/Build/Step/CheckObject.zig @@ -539,7 +539,7 @@ const MachODumper = struct { strings: []const u8, }; - fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8) ![]const u8 { + fn parseAndDump(step: *Step, bytes: []const u8) ![]const u8 { const gpa = step.owner.allocator; var stream = std.io.fixedBufferStream(bytes); const reader = stream.reader(); @@ -556,7 +556,7 @@ const MachODumper = struct { var sections = std.ArrayList(macho.section_64).init(gpa); var imports = std.ArrayList([]const u8).init(gpa); - var it = LoadCommandIterator{ + var it: LoadCommandIterator = .{ .ncmds = hdr.ncmds, .buffer = bytes[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], }; diff --git a/lib/std/io.zig b/lib/std/io.zig index e7a4476c0f..64ed9f757e 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -10,6 +10,7 @@ const fs = std.fs; const mem = std.mem; const meta = std.meta; const File = std.fs.File; +const Allocator = std.mem.Allocator; pub const Mode = enum { /// I/O operates normally, waiting for the operating system syscalls to complete. @@ -105,7 +106,255 @@ pub fn getStdIn() File { }; } -pub const Reader = @import("io/reader.zig").Reader; +pub fn GenericReader( + comptime Context: type, + comptime ReadError: type, + /// Returns the number of bytes read. It may be less than buffer.len. + /// If the number of bytes read is 0, it means end of stream. + /// End of stream is not an error condition. + comptime readFn: fn (context: Context, buffer: []u8) ReadError!usize, +) type { + return struct { + context: Context, + + pub const Error = ReadError; + pub const NoEofError = ReadError || error{ + EndOfStream, + }; + + pub inline fn read(self: Self, buffer: []u8) Error!usize { + return readFn(self.context, buffer); + } + + pub inline fn readAll(self: Self, buffer: []u8) Error!usize { + return @errorCast(self.any().readAll(buffer)); + } + + pub inline fn readAtLeast(self: Self, buffer: []u8, len: usize) Error!usize { + return @errorCast(self.any().readAtLeast(buffer, len)); + } + + pub inline fn readNoEof(self: Self, buf: []u8) NoEofError!void { + return @errorCast(self.any().readNoEof(buf)); + } + + pub inline fn readAllArrayList( + self: Self, + array_list: *std.ArrayList(u8), + max_append_size: usize, + ) (error{StreamTooLong} || Error)!void { + return @errorCast(self.any().readAllArrayList(array_list, max_append_size)); + } + + pub inline fn readAllArrayListAligned( + self: Self, + comptime alignment: ?u29, + array_list: *std.ArrayListAligned(u8, alignment), + max_append_size: usize, + ) (error{StreamTooLong} || Error)!void { + return @errorCast(self.any().readAllArrayListAligned( + alignment, + array_list, + max_append_size, + )); + } + + pub inline fn readAllAlloc( + self: Self, + allocator: Allocator, + max_size: usize, + ) (Error || error{StreamTooLong})![]u8 { + return @errorCast(self.any().readAllAlloc(allocator, max_size)); + } + + pub inline fn readUntilDelimiterArrayList( + self: Self, + array_list: *std.ArrayList(u8), + delimiter: u8, + max_size: usize, + ) (NoEofError || error{StreamTooLong})!void { + return @errorCast(self.any().readUntilDelimiterArrayList( + array_list, + delimiter, + max_size, + )); + } + + pub inline fn readUntilDelimiterAlloc( + self: Self, + allocator: Allocator, + delimiter: u8, + max_size: usize, + ) (NoEofError || error{StreamTooLong})![]u8 { + return @errorCast(self.any().readUntilDelimiterAlloc( + allocator, + delimiter, + max_size, + )); + } + + pub inline fn readUntilDelimiter( + self: Self, + buf: []u8, + delimiter: u8, + ) (NoEofError || error{StreamTooLong})![]u8 { + return @errorCast(self.any().readUntilDelimiter(buf, delimiter)); + } + + pub inline fn readUntilDelimiterOrEofAlloc( + self: Self, + allocator: Allocator, + delimiter: u8, + max_size: usize, + ) (Error || error{StreamTooLong})!?[]u8 { + return @errorCast(self.any().readUntilDelimiterOrEofAlloc( + allocator, + delimiter, + max_size, + )); + } + + pub inline fn readUntilDelimiterOrEof( + self: Self, + buf: []u8, + delimiter: u8, + ) (Error || error{StreamTooLong})!?[]u8 { + return @errorCast(self.any().readUntilDelimiterOrEof(buf, delimiter)); + } + + pub inline fn streamUntilDelimiter( + self: Self, + writer: anytype, + delimiter: u8, + optional_max_size: ?usize, + ) (NoEofError || error{StreamTooLong} || @TypeOf(writer).Error)!void { + return @errorCast(self.any().streamUntilDelimiter( + writer, + delimiter, + optional_max_size, + )); + } + + pub inline fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) Error!void { + return @errorCast(self.any().skipUntilDelimiterOrEof(delimiter)); + } + + pub inline fn readByte(self: Self) NoEofError!u8 { + return @errorCast(self.any().readByte()); + } + + pub inline fn readByteSigned(self: Self) NoEofError!i8 { + return @errorCast(self.any().readByteSigned()); + } + + pub inline fn readBytesNoEof( + self: Self, + comptime num_bytes: usize, + ) NoEofError![num_bytes]u8 { + return @errorCast(self.any().readBytesNoEof(num_bytes)); + } + + pub inline fn readIntoBoundedBytes( + self: Self, + comptime num_bytes: usize, + bounded: *std.BoundedArray(u8, num_bytes), + ) Error!void { + return @errorCast(self.any().readIntoBoundedBytes(num_bytes, bounded)); + } + + pub inline fn readBoundedBytes( + self: Self, + comptime num_bytes: usize, + ) Error!std.BoundedArray(u8, num_bytes) { + return @errorCast(self.any().readBoundedBytes(num_bytes)); + } + + pub inline fn readIntNative(self: Self, comptime T: type) NoEofError!T { + return @errorCast(self.any().readIntNative(T)); + } + + pub inline fn readIntForeign(self: Self, comptime T: type) NoEofError!T { + return @errorCast(self.any().readIntForeign(T)); + } + + pub inline fn readIntLittle(self: Self, comptime T: type) NoEofError!T { + return @errorCast(self.any().readIntLittle(T)); + } + + pub inline fn readIntBig(self: Self, comptime T: type) NoEofError!T { + return @errorCast(self.any().readIntBig(T)); + } + + pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T { + return @errorCast(self.any().readInt(T, endian)); + } + + pub inline fn readVarInt( + self: Self, + comptime ReturnType: type, + endian: std.builtin.Endian, + size: usize, + ) NoEofError!ReturnType { + return @errorCast(self.any().readVarInt(ReturnType, endian, size)); + } + + pub const SkipBytesOptions = AnyReader.SkipBytesOptions; + + pub inline fn skipBytes( + self: Self, + num_bytes: u64, + comptime options: SkipBytesOptions, + ) NoEofError!void { + return @errorCast(self.any().skipBytes(num_bytes, options)); + } + + pub inline fn isBytes(self: Self, slice: []const u8) Error!bool { + return @errorCast(self.any().isBytes(slice)); + } + + pub inline fn readStruct(self: Self, comptime T: type) NoEofError!T { + return @errorCast(self.any().readStruct(T)); + } + + pub inline fn readStructBig(self: Self, comptime T: type) NoEofError!T { + return @errorCast(self.any().readStructBig(T)); + } + + pub const ReadEnumError = Error || error{ + /// An integer was read, but it did not match any of the tags in the supplied enum. + InvalidValue, + }; + + pub inline fn readEnum( + self: Self, + comptime Enum: type, + endian: std.builtin.Endian, + ) ReadEnumError!Enum { + return @errorCast(self.any().readEnum(Enum, endian)); + } + + pub inline fn any(self: *const Self) AnyReader { + return .{ + .context = @ptrCast(&self.context), + .readFn = typeErasedReadFn, + }; + } + + const Self = @This(); + + fn typeErasedReadFn(context: *const anyopaque, buffer: []u8) anyerror!usize { + const ptr: *const Context = @alignCast(@ptrCast(context)); + return readFn(ptr.*, buffer); + } + }; +} + +/// Deprecated; consider switching to `AnyReader` or use `GenericReader` +/// to use previous API. +pub const Reader = GenericReader; + +pub const AnyReader = @import("io/Reader.zig"); + pub const Writer = @import("io/writer.zig").Writer; pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; @@ -168,7 +417,7 @@ test "null_writer" { } pub fn poll( - allocator: std.mem.Allocator, + allocator: Allocator, comptime StreamEnum: type, files: PollFiles(StreamEnum), ) Poller(StreamEnum) { @@ -418,6 +667,7 @@ pub fn PollFiles(comptime StreamEnum: type) type { } test { + _ = AnyReader; _ = @import("io/bit_reader.zig"); _ = @import("io/bit_writer.zig"); _ = @import("io/buffered_atomic_file.zig"); @@ -427,7 +677,6 @@ test { _ = @import("io/counting_writer.zig"); _ = @import("io/counting_reader.zig"); _ = @import("io/fixed_buffer_stream.zig"); - _ = @import("io/reader.zig"); _ = @import("io/writer.zig"); _ = @import("io/peek_stream.zig"); _ = @import("io/seekable_stream.zig"); diff --git a/lib/std/io/Reader.zig b/lib/std/io/Reader.zig new file mode 100644 index 0000000000..4a03eb0b54 --- /dev/null +++ b/lib/std/io/Reader.zig @@ -0,0 +1,395 @@ +context: *const anyopaque, +readFn: *const fn (context: *const anyopaque, buffer: []u8) anyerror!usize, + +pub const Error = anyerror; + +/// Returns the number of bytes read. It may be less than buffer.len. +/// If the number of bytes read is 0, it means end of stream. +/// End of stream is not an error condition. +pub fn read(self: Self, buffer: []u8) anyerror!usize { + return self.readFn(self.context, buffer); +} + +/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it +/// means the stream reached the end. Reaching the end of a stream is not an error +/// condition. +pub fn readAll(self: Self, buffer: []u8) anyerror!usize { + return readAtLeast(self, buffer, buffer.len); +} + +/// Returns the number of bytes read, calling the underlying read +/// function the minimal number of times until the buffer has at least +/// `len` bytes filled. If the number read is less than `len` it means +/// the stream reached the end. Reaching the end of the stream is not +/// an error condition. +pub fn readAtLeast(self: Self, buffer: []u8, len: usize) anyerror!usize { + assert(len <= buffer.len); + var index: usize = 0; + while (index < len) { + const amt = try self.read(buffer[index..]); + if (amt == 0) break; + index += amt; + } + return index; +} + +/// If the number read would be smaller than `buf.len`, `error.EndOfStream` is returned instead. +pub fn readNoEof(self: Self, buf: []u8) anyerror!void { + const amt_read = try self.readAll(buf); + if (amt_read < buf.len) return error.EndOfStream; +} + +/// Appends to the `std.ArrayList` contents by reading from the stream +/// until end of stream is found. +/// If the number of bytes appended would exceed `max_append_size`, +/// `error.StreamTooLong` is returned +/// and the `std.ArrayList` has exactly `max_append_size` bytes appended. +pub fn readAllArrayList( + self: Self, + array_list: *std.ArrayList(u8), + max_append_size: usize, +) anyerror!void { + return self.readAllArrayListAligned(null, array_list, max_append_size); +} + +pub fn readAllArrayListAligned( + self: Self, + comptime alignment: ?u29, + array_list: *std.ArrayListAligned(u8, alignment), + max_append_size: usize, +) anyerror!void { + try array_list.ensureTotalCapacity(@min(max_append_size, 4096)); + const original_len = array_list.items.len; + var start_index: usize = original_len; + while (true) { + array_list.expandToCapacity(); + const dest_slice = array_list.items[start_index..]; + const bytes_read = try self.readAll(dest_slice); + start_index += bytes_read; + + if (start_index - original_len > max_append_size) { + array_list.shrinkAndFree(original_len + max_append_size); + return error.StreamTooLong; + } + + if (bytes_read != dest_slice.len) { + array_list.shrinkAndFree(start_index); + return; + } + + // This will trigger ArrayList to expand superlinearly at whatever its growth rate is. + try array_list.ensureTotalCapacity(start_index + 1); + } +} + +/// Allocates enough memory to hold all the contents of the stream. If the allocated +/// memory would be greater than `max_size`, returns `error.StreamTooLong`. +/// Caller owns returned memory. +/// If this function returns an error, the contents from the stream read so far are lost. +pub fn readAllAlloc(self: Self, allocator: mem.Allocator, max_size: usize) anyerror![]u8 { + var array_list = std.ArrayList(u8).init(allocator); + defer array_list.deinit(); + try self.readAllArrayList(&array_list, max_size); + return try array_list.toOwnedSlice(); +} + +/// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead. +/// Replaces the `std.ArrayList` contents by reading from the stream until `delimiter` is found. +/// Does not include the delimiter in the result. +/// If the `std.ArrayList` length would exceed `max_size`, `error.StreamTooLong` is returned and the +/// `std.ArrayList` is populated with `max_size` bytes from the stream. +pub fn readUntilDelimiterArrayList( + self: Self, + array_list: *std.ArrayList(u8), + delimiter: u8, + max_size: usize, +) anyerror!void { + array_list.shrinkRetainingCapacity(0); + try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size); +} + +/// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead. +/// Allocates enough memory to read until `delimiter`. If the allocated +/// memory would be greater than `max_size`, returns `error.StreamTooLong`. +/// Caller owns returned memory. +/// If this function returns an error, the contents from the stream read so far are lost. +pub fn readUntilDelimiterAlloc( + self: Self, + allocator: mem.Allocator, + delimiter: u8, + max_size: usize, +) anyerror![]u8 { + var array_list = std.ArrayList(u8).init(allocator); + defer array_list.deinit(); + try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size); + return try array_list.toOwnedSlice(); +} + +/// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead. +/// Reads from the stream until specified byte is found. If the buffer is not +/// large enough to hold the entire contents, `error.StreamTooLong` is returned. +/// If end-of-stream is found, `error.EndOfStream` is returned. +/// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The +/// delimiter byte is written to the output buffer but is not included +/// in the returned slice. +pub fn readUntilDelimiter(self: Self, buf: []u8, delimiter: u8) anyerror![]u8 { + var fbs = std.io.fixedBufferStream(buf); + try self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len); + const output = fbs.getWritten(); + buf[output.len] = delimiter; // emulating old behaviour + return output; +} + +/// Deprecated: use `streamUntilDelimiter` with ArrayList's (or any other's) writer instead. +/// Allocates enough memory to read until `delimiter` or end-of-stream. +/// If the allocated memory would be greater than `max_size`, returns +/// `error.StreamTooLong`. If end-of-stream is found, returns the rest +/// of the stream. If this function is called again after that, returns +/// null. +/// Caller owns returned memory. +/// If this function returns an error, the contents from the stream read so far are lost. +pub fn readUntilDelimiterOrEofAlloc( + self: Self, + allocator: mem.Allocator, + delimiter: u8, + max_size: usize, +) anyerror!?[]u8 { + var array_list = std.ArrayList(u8).init(allocator); + defer array_list.deinit(); + self.streamUntilDelimiter(array_list.writer(), delimiter, max_size) catch |err| switch (err) { + error.EndOfStream => if (array_list.items.len == 0) { + return null; + }, + else => |e| return e, + }; + return try array_list.toOwnedSlice(); +} + +/// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead. +/// Reads from the stream until specified byte is found. If the buffer is not +/// large enough to hold the entire contents, `error.StreamTooLong` is returned. +/// If end-of-stream is found, returns the rest of the stream. If this +/// function is called again after that, returns null. +/// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The +/// delimiter byte is written to the output buffer but is not included +/// in the returned slice. +pub fn readUntilDelimiterOrEof(self: Self, buf: []u8, delimiter: u8) anyerror!?[]u8 { + var fbs = std.io.fixedBufferStream(buf); + self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len) catch |err| switch (err) { + error.EndOfStream => if (fbs.getWritten().len == 0) { + return null; + }, + + else => |e| return e, + }; + const output = fbs.getWritten(); + buf[output.len] = delimiter; // emulating old behaviour + return output; +} + +/// Appends to the `writer` contents by reading from the stream until `delimiter` is found. +/// Does not write the delimiter itself. +/// If `optional_max_size` is not null and amount of written bytes exceeds `optional_max_size`, +/// returns `error.StreamTooLong` and finishes appending. +/// If `optional_max_size` is null, appending is unbounded. +pub fn streamUntilDelimiter( + self: Self, + writer: anytype, + delimiter: u8, + optional_max_size: ?usize, +) anyerror!void { + if (optional_max_size) |max_size| { + for (0..max_size) |_| { + const byte: u8 = try self.readByte(); + if (byte == delimiter) return; + try writer.writeByte(byte); + } + return error.StreamTooLong; + } else { + while (true) { + const byte: u8 = try self.readByte(); + if (byte == delimiter) return; + try writer.writeByte(byte); + } + // Can not throw `error.StreamTooLong` since there are no boundary. + } +} + +/// Reads from the stream until specified byte is found, discarding all data, +/// including the delimiter. +/// If end-of-stream is found, this function succeeds. +pub fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) anyerror!void { + while (true) { + const byte = self.readByte() catch |err| switch (err) { + error.EndOfStream => return, + else => |e| return e, + }; + if (byte == delimiter) return; + } +} + +/// Reads 1 byte from the stream or returns `error.EndOfStream`. +pub fn readByte(self: Self) anyerror!u8 { + var result: [1]u8 = undefined; + const amt_read = try self.read(result[0..]); + if (amt_read < 1) return error.EndOfStream; + return result[0]; +} + +/// Same as `readByte` except the returned byte is signed. +pub fn readByteSigned(self: Self) anyerror!i8 { + return @as(i8, @bitCast(try self.readByte())); +} + +/// Reads exactly `num_bytes` bytes and returns as an array. +/// `num_bytes` must be comptime-known +pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) anyerror![num_bytes]u8 { + var bytes: [num_bytes]u8 = undefined; + try self.readNoEof(&bytes); + return bytes; +} + +/// Reads bytes until `bounded.len` is equal to `num_bytes`, +/// or the stream ends. +/// +/// * it is assumed that `num_bytes` will not exceed `bounded.capacity()` +pub fn readIntoBoundedBytes( + self: Self, + comptime num_bytes: usize, + bounded: *std.BoundedArray(u8, num_bytes), +) anyerror!void { + while (bounded.len < num_bytes) { + // get at most the number of bytes free in the bounded array + const bytes_read = try self.read(bounded.unusedCapacitySlice()); + if (bytes_read == 0) return; + + // bytes_read will never be larger than @TypeOf(bounded.len) + // due to `self.read` being bounded by `bounded.unusedCapacitySlice()` + bounded.len += @as(@TypeOf(bounded.len), @intCast(bytes_read)); + } +} + +/// Reads at most `num_bytes` and returns as a bounded array. +pub fn readBoundedBytes(self: Self, comptime num_bytes: usize) anyerror!std.BoundedArray(u8, num_bytes) { + var result = std.BoundedArray(u8, num_bytes){}; + try self.readIntoBoundedBytes(num_bytes, &result); + return result; +} + +/// Reads a native-endian integer +pub fn readIntNative(self: Self, comptime T: type) anyerror!T { + const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))); + return mem.readIntNative(T, &bytes); +} + +/// Reads a foreign-endian integer +pub fn readIntForeign(self: Self, comptime T: type) anyerror!T { + const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))); + return mem.readIntForeign(T, &bytes); +} + +pub fn readIntLittle(self: Self, comptime T: type) anyerror!T { + const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))); + return mem.readIntLittle(T, &bytes); +} + +pub fn readIntBig(self: Self, comptime T: type) anyerror!T { + const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))); + return mem.readIntBig(T, &bytes); +} + +pub fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T { + const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))); + return mem.readInt(T, &bytes, endian); +} + +pub fn readVarInt( + self: Self, + comptime ReturnType: type, + endian: std.builtin.Endian, + size: usize, +) anyerror!ReturnType { + assert(size <= @sizeOf(ReturnType)); + var bytes_buf: [@sizeOf(ReturnType)]u8 = undefined; + const bytes = bytes_buf[0..size]; + try self.readNoEof(bytes); + return mem.readVarInt(ReturnType, bytes, endian); +} + +/// Optional parameters for `skipBytes` +pub const SkipBytesOptions = struct { + buf_size: usize = 512, +}; + +// `num_bytes` is a `u64` to match `off_t` +/// Reads `num_bytes` bytes from the stream and discards them +pub fn skipBytes(self: Self, num_bytes: u64, comptime options: SkipBytesOptions) anyerror!void { + var buf: [options.buf_size]u8 = undefined; + var remaining = num_bytes; + + while (remaining > 0) { + const amt = @min(remaining, options.buf_size); + try self.readNoEof(buf[0..amt]); + remaining -= amt; + } +} + +/// Reads `slice.len` bytes from the stream and returns if they are the same as the passed slice +pub fn isBytes(self: Self, slice: []const u8) anyerror!bool { + var i: usize = 0; + var matches = true; + while (i < slice.len) : (i += 1) { + if (slice[i] != try self.readByte()) { + matches = false; + } + } + return matches; +} + +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]; +} + +pub fn readStructBig(self: Self, comptime T: type) anyerror!T { + var res = try self.readStruct(T); + if (native_endian != std.builtin.Endian.Big) { + mem.byteSwapAllFields(T, &res); + } + return res; +} + +/// Reads an integer with the same size as the given enum's tag type. If the integer matches +/// an enum tag, casts the integer to the enum tag and returns it. Otherwise, returns an `error.InvalidValue`. +/// TODO optimization taking advantage of most fields being in order +pub fn readEnum(self: Self, comptime Enum: type, endian: std.builtin.Endian) anyerror!Enum { + const E = error{ + /// An integer was read, but it did not match any of the tags in the supplied enum. + InvalidValue, + }; + const type_info = @typeInfo(Enum).Enum; + const tag = try self.readInt(type_info.tag_type, endian); + + inline for (std.meta.fields(Enum)) |field| { + if (tag == field.value) { + return @field(Enum, field.name); + } + } + + return E.InvalidValue; +} + +const std = @import("../std.zig"); +const Self = @This(); +const math = std.math; +const assert = std.debug.assert; +const mem = std.mem; +const testing = std.testing; +const native_endian = @import("builtin").target.cpu.arch.endian(); + +test { + _ = @import("Reader/test.zig"); +} diff --git a/lib/std/io/Reader/test.zig b/lib/std/io/Reader/test.zig new file mode 100644 index 0000000000..42fce98457 --- /dev/null +++ b/lib/std/io/Reader/test.zig @@ -0,0 +1,371 @@ +const std = @import("../../std.zig"); +const testing = std.testing; + +test "Reader" { + var buf = "a\x02".*; + var fis = std.io.fixedBufferStream(&buf); + const reader = fis.reader(); + try testing.expect((try reader.readByte()) == 'a'); + try testing.expect((try reader.readEnum(enum(u8) { + a = 0, + b = 99, + c = 2, + d = 3, + }, undefined)) == .c); + try testing.expectError(error.EndOfStream, reader.readByte()); +} + +test "Reader.isBytes" { + var fis = std.io.fixedBufferStream("foobar"); + const reader = fis.reader(); + try testing.expectEqual(true, try reader.isBytes("foo")); + try testing.expectEqual(false, try reader.isBytes("qux")); +} + +test "Reader.skipBytes" { + var fis = std.io.fixedBufferStream("foobar"); + const reader = fis.reader(); + try reader.skipBytes(3, .{}); + try testing.expect(try reader.isBytes("bar")); + try reader.skipBytes(0, .{}); + try testing.expectError(error.EndOfStream, reader.skipBytes(1, .{})); +} + +test "Reader.readUntilDelimiterArrayList returns ArrayLists with bytes read until the delimiter, then EndOfStream" { + const a = std.testing.allocator; + var list = std.ArrayList(u8).init(a); + defer list.deinit(); + + var fis = std.io.fixedBufferStream("0000\n1234\n"); + const reader = fis.reader(); + + try reader.readUntilDelimiterArrayList(&list, '\n', 5); + try std.testing.expectEqualStrings("0000", list.items); + try reader.readUntilDelimiterArrayList(&list, '\n', 5); + try std.testing.expectEqualStrings("1234", list.items); + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5)); +} + +test "Reader.readUntilDelimiterArrayList returns an empty ArrayList" { + const a = std.testing.allocator; + var list = std.ArrayList(u8).init(a); + defer list.deinit(); + + var fis = std.io.fixedBufferStream("\n"); + const reader = fis.reader(); + + try reader.readUntilDelimiterArrayList(&list, '\n', 5); + try std.testing.expectEqualStrings("", list.items); +} + +test "Reader.readUntilDelimiterArrayList returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { + const a = std.testing.allocator; + var list = std.ArrayList(u8).init(a); + defer list.deinit(); + + var fis = std.io.fixedBufferStream("1234567\n"); + const reader = fis.reader(); + + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterArrayList(&list, '\n', 5)); + try std.testing.expectEqualStrings("12345", list.items); + try reader.readUntilDelimiterArrayList(&list, '\n', 5); + try std.testing.expectEqualStrings("67", list.items); +} + +test "Reader.readUntilDelimiterArrayList returns EndOfStream" { + const a = std.testing.allocator; + var list = std.ArrayList(u8).init(a); + defer list.deinit(); + + var fis = std.io.fixedBufferStream("1234"); + const reader = fis.reader(); + + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5)); + try std.testing.expectEqualStrings("1234", list.items); +} + +test "Reader.readUntilDelimiterAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" { + const a = std.testing.allocator; + + var fis = std.io.fixedBufferStream("0000\n1234\n"); + const reader = fis.reader(); + + { + var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); + defer a.free(result); + try std.testing.expectEqualStrings("0000", result); + } + + { + var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); + defer a.free(result); + try std.testing.expectEqualStrings("1234", result); + } + + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5)); +} + +test "Reader.readUntilDelimiterAlloc returns an empty ArrayList" { + const a = std.testing.allocator; + + var fis = std.io.fixedBufferStream("\n"); + const reader = fis.reader(); + + { + var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); + defer a.free(result); + try std.testing.expectEqualStrings("", result); + } +} + +test "Reader.readUntilDelimiterAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { + const a = std.testing.allocator; + + var fis = std.io.fixedBufferStream("1234567\n"); + const reader = fis.reader(); + + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterAlloc(a, '\n', 5)); + + var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); + defer a.free(result); + try std.testing.expectEqualStrings("67", result); +} + +test "Reader.readUntilDelimiterAlloc returns EndOfStream" { + const a = std.testing.allocator; + + var fis = std.io.fixedBufferStream("1234"); + const reader = fis.reader(); + + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5)); +} + +test "Reader.readUntilDelimiter returns bytes read until the delimiter" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("0000\n1234\n"); + const reader = fis.reader(); + try std.testing.expectEqualStrings("0000", try reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns an empty string" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("\n"); + const reader = fis.reader(); + try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns StreamTooLong, then an empty string" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("12345\n"); + const reader = fis.reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns StreamTooLong, then bytes read until the delimiter" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("1234567\n"); + const reader = fis.reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectEqualStrings("67", try reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns EndOfStream" { + { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream(""); + const reader = fis.reader(); + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); + } + { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("1234"); + const reader = fis.reader(); + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); + } +} + +test "Reader.readUntilDelimiter returns bytes read until delimiter, then EndOfStream" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("1234\n"); + const reader = fis.reader(); + try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter returns StreamTooLong, then EndOfStream" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("12345"); + const reader = fis.reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); +} + +test "Reader.readUntilDelimiter writes all bytes read to the output buffer" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("0000\n12345"); + const reader = fis.reader(); + _ = try reader.readUntilDelimiter(&buf, '\n'); + try std.testing.expectEqualStrings("0000\n", &buf); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); + try std.testing.expectEqualStrings("12345", &buf); +} + +test "Reader.readUntilDelimiterOrEofAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" { + const a = std.testing.allocator; + + var fis = std.io.fixedBufferStream("0000\n1234\n"); + const reader = fis.reader(); + + { + var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; + defer a.free(result); + try std.testing.expectEqualStrings("0000", result); + } + + { + var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; + defer a.free(result); + try std.testing.expectEqualStrings("1234", result); + } + + try std.testing.expect((try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)) == null); +} + +test "Reader.readUntilDelimiterOrEofAlloc returns an empty ArrayList" { + const a = std.testing.allocator; + + var fis = std.io.fixedBufferStream("\n"); + const reader = fis.reader(); + + { + var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; + defer a.free(result); + try std.testing.expectEqualStrings("", result); + } +} + +test "Reader.readUntilDelimiterOrEofAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { + const a = std.testing.allocator; + + var fis = std.io.fixedBufferStream("1234567\n"); + const reader = fis.reader(); + + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)); + + var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; + defer a.free(result); + try std.testing.expectEqualStrings("67", result); +} + +test "Reader.readUntilDelimiterOrEof returns bytes read until the delimiter" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("0000\n1234\n"); + const reader = fis.reader(); + try std.testing.expectEqualStrings("0000", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); + try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof returns an empty string" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("\n"); + const reader = fis.reader(); + try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof returns StreamTooLong, then an empty string" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("12345\n"); + const reader = fis.reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); + try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof returns StreamTooLong, then bytes read until the delimiter" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("1234567\n"); + const reader = fis.reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); + try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof returns null" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream(""); + const reader = fis.reader(); + try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null); +} + +test "Reader.readUntilDelimiterOrEof returns bytes read until delimiter, then null" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("1234\n"); + const reader = fis.reader(); + try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); + try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null); +} + +test "Reader.readUntilDelimiterOrEof returns bytes read until end-of-stream" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("1234"); + const reader = fis.reader(); + try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof returns StreamTooLong, then bytes read until end-of-stream" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("1234567"); + const reader = fis.reader(); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); + try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); +} + +test "Reader.readUntilDelimiterOrEof writes all bytes read to the output buffer" { + var buf: [5]u8 = undefined; + var fis = std.io.fixedBufferStream("0000\n12345"); + const reader = fis.reader(); + _ = try reader.readUntilDelimiterOrEof(&buf, '\n'); + try std.testing.expectEqualStrings("0000\n", &buf); + try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); + try std.testing.expectEqualStrings("12345", &buf); +} + +test "Reader.streamUntilDelimiter writes all bytes without delimiter to the output" { + const input_string = "some_string_with_delimiter!"; + var input_fbs = std.io.fixedBufferStream(input_string); + const reader = input_fbs.reader(); + + var output: [input_string.len]u8 = undefined; + var output_fbs = std.io.fixedBufferStream(&output); + const writer = output_fbs.writer(); + + try reader.streamUntilDelimiter(writer, '!', input_fbs.buffer.len); + try std.testing.expectEqualStrings("some_string_with_delimiter", output_fbs.getWritten()); + try std.testing.expectError(error.EndOfStream, reader.streamUntilDelimiter(writer, '!', input_fbs.buffer.len)); + + input_fbs.reset(); + output_fbs.reset(); + + try std.testing.expectError(error.StreamTooLong, reader.streamUntilDelimiter(writer, '!', 5)); +} + +test "Reader.readBoundedBytes correctly reads into a new bounded array" { + const test_string = "abcdefg"; + var fis = std.io.fixedBufferStream(test_string); + const reader = fis.reader(); + + var array = try reader.readBoundedBytes(10000); + try testing.expectEqualStrings(array.slice(), test_string); +} + +test "Reader.readIntoBoundedBytes correctly reads into a provided bounded array" { + const test_string = "abcdefg"; + var fis = std.io.fixedBufferStream(test_string); + const reader = fis.reader(); + + var bounded_array = std.BoundedArray(u8, 10000){}; + + // compile time error if the size is not the same at the provided `bounded.capacity()` + try reader.readIntoBoundedBytes(10000, &bounded_array); + try testing.expectEqualStrings(bounded_array.slice(), test_string); +} diff --git a/lib/std/io/reader.zig b/lib/std/io/reader.zig deleted file mode 100644 index 0a02e17b32..0000000000 --- a/lib/std/io/reader.zig +++ /dev/null @@ -1,757 +0,0 @@ -const std = @import("../std.zig"); -const math = std.math; -const assert = std.debug.assert; -const mem = std.mem; -const testing = std.testing; -const native_endian = @import("builtin").target.cpu.arch.endian(); - -pub fn Reader( - comptime Context: type, - comptime ReadError: type, - /// Returns the number of bytes read. It may be less than buffer.len. - /// If the number of bytes read is 0, it means end of stream. - /// End of stream is not an error condition. - comptime readFn: fn (context: Context, buffer: []u8) ReadError!usize, -) type { - return struct { - pub const Error = ReadError; - - context: Context, - - const Self = @This(); - - /// Returns the number of bytes read. It may be less than buffer.len. - /// If the number of bytes read is 0, it means end of stream. - /// End of stream is not an error condition. - pub fn read(self: Self, buffer: []u8) Error!usize { - return readFn(self.context, buffer); - } - - /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it - /// means the stream reached the end. Reaching the end of a stream is not an error - /// condition. - pub fn readAll(self: Self, buffer: []u8) Error!usize { - return readAtLeast(self, buffer, buffer.len); - } - - /// Returns the number of bytes read, calling the underlying read - /// function the minimal number of times until the buffer has at least - /// `len` bytes filled. If the number read is less than `len` it means - /// the stream reached the end. Reaching the end of the stream is not - /// an error condition. - pub fn readAtLeast(self: Self, buffer: []u8, len: usize) Error!usize { - assert(len <= buffer.len); - var index: usize = 0; - while (index < len) { - const amt = try self.read(buffer[index..]); - if (amt == 0) break; - index += amt; - } - return index; - } - - /// If the number read would be smaller than `buf.len`, `error.EndOfStream` is returned instead. - pub fn readNoEof(self: Self, buf: []u8) (Error || error{EndOfStream})!void { - const amt_read = try self.readAll(buf); - if (amt_read < buf.len) return error.EndOfStream; - } - - /// Appends to the `std.ArrayList` contents by reading from the stream - /// until end of stream is found. - /// If the number of bytes appended would exceed `max_append_size`, - /// `error.StreamTooLong` is returned - /// and the `std.ArrayList` has exactly `max_append_size` bytes appended. - pub fn readAllArrayList(self: Self, array_list: *std.ArrayList(u8), max_append_size: usize) !void { - return self.readAllArrayListAligned(null, array_list, max_append_size); - } - - pub fn readAllArrayListAligned( - self: Self, - comptime alignment: ?u29, - array_list: *std.ArrayListAligned(u8, alignment), - max_append_size: usize, - ) !void { - try array_list.ensureTotalCapacity(@min(max_append_size, 4096)); - const original_len = array_list.items.len; - var start_index: usize = original_len; - while (true) { - array_list.expandToCapacity(); - const dest_slice = array_list.items[start_index..]; - const bytes_read = try self.readAll(dest_slice); - start_index += bytes_read; - - if (start_index - original_len > max_append_size) { - array_list.shrinkAndFree(original_len + max_append_size); - return error.StreamTooLong; - } - - if (bytes_read != dest_slice.len) { - array_list.shrinkAndFree(start_index); - return; - } - - // This will trigger ArrayList to expand superlinearly at whatever its growth rate is. - try array_list.ensureTotalCapacity(start_index + 1); - } - } - - /// Allocates enough memory to hold all the contents of the stream. If the allocated - /// memory would be greater than `max_size`, returns `error.StreamTooLong`. - /// Caller owns returned memory. - /// If this function returns an error, the contents from the stream read so far are lost. - pub fn readAllAlloc(self: Self, allocator: mem.Allocator, max_size: usize) ![]u8 { - var array_list = std.ArrayList(u8).init(allocator); - defer array_list.deinit(); - try self.readAllArrayList(&array_list, max_size); - return try array_list.toOwnedSlice(); - } - - /// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead. - /// Replaces the `std.ArrayList` contents by reading from the stream until `delimiter` is found. - /// Does not include the delimiter in the result. - /// If the `std.ArrayList` length would exceed `max_size`, `error.StreamTooLong` is returned and the - /// `std.ArrayList` is populated with `max_size` bytes from the stream. - pub fn readUntilDelimiterArrayList( - self: Self, - array_list: *std.ArrayList(u8), - delimiter: u8, - max_size: usize, - ) !void { - array_list.shrinkRetainingCapacity(0); - try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size); - } - - /// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead. - /// Allocates enough memory to read until `delimiter`. If the allocated - /// memory would be greater than `max_size`, returns `error.StreamTooLong`. - /// Caller owns returned memory. - /// If this function returns an error, the contents from the stream read so far are lost. - pub fn readUntilDelimiterAlloc( - self: Self, - allocator: mem.Allocator, - delimiter: u8, - max_size: usize, - ) ![]u8 { - var array_list = std.ArrayList(u8).init(allocator); - defer array_list.deinit(); - try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size); - return try array_list.toOwnedSlice(); - } - - /// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead. - /// Reads from the stream until specified byte is found. If the buffer is not - /// large enough to hold the entire contents, `error.StreamTooLong` is returned. - /// If end-of-stream is found, `error.EndOfStream` is returned. - /// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The - /// delimiter byte is written to the output buffer but is not included - /// in the returned slice. - pub fn readUntilDelimiter(self: Self, buf: []u8, delimiter: u8) ![]u8 { - var fbs = std.io.fixedBufferStream(buf); - try self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len); - const output = fbs.getWritten(); - buf[output.len] = delimiter; // emulating old behaviour - return output; - } - - /// Deprecated: use `streamUntilDelimiter` with ArrayList's (or any other's) writer instead. - /// Allocates enough memory to read until `delimiter` or end-of-stream. - /// If the allocated memory would be greater than `max_size`, returns - /// `error.StreamTooLong`. If end-of-stream is found, returns the rest - /// of the stream. If this function is called again after that, returns - /// null. - /// Caller owns returned memory. - /// If this function returns an error, the contents from the stream read so far are lost. - pub fn readUntilDelimiterOrEofAlloc( - self: Self, - allocator: mem.Allocator, - delimiter: u8, - max_size: usize, - ) !?[]u8 { - var array_list = std.ArrayList(u8).init(allocator); - defer array_list.deinit(); - self.streamUntilDelimiter(array_list.writer(), delimiter, max_size) catch |err| switch (err) { - error.EndOfStream => if (array_list.items.len == 0) { - return null; - }, - else => |e| return e, - }; - return try array_list.toOwnedSlice(); - } - - /// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead. - /// Reads from the stream until specified byte is found. If the buffer is not - /// large enough to hold the entire contents, `error.StreamTooLong` is returned. - /// If end-of-stream is found, returns the rest of the stream. If this - /// function is called again after that, returns null. - /// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The - /// delimiter byte is written to the output buffer but is not included - /// in the returned slice. - pub fn readUntilDelimiterOrEof(self: Self, buf: []u8, delimiter: u8) !?[]u8 { - var fbs = std.io.fixedBufferStream(buf); - self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len) catch |err| switch (err) { - error.EndOfStream => if (fbs.getWritten().len == 0) { - return null; - }, - - else => |e| return e, - }; - const output = fbs.getWritten(); - buf[output.len] = delimiter; // emulating old behaviour - return output; - } - - /// Appends to the `writer` contents by reading from the stream until `delimiter` is found. - /// Does not write the delimiter itself. - /// If `optional_max_size` is not null and amount of written bytes exceeds `optional_max_size`, - /// returns `error.StreamTooLong` and finishes appending. - /// If `optional_max_size` is null, appending is unbounded. - pub fn streamUntilDelimiter(self: Self, writer: anytype, delimiter: u8, optional_max_size: ?usize) (Error || error{ EndOfStream, StreamTooLong } || @TypeOf(writer).Error)!void { - if (optional_max_size) |max_size| { - for (0..max_size) |_| { - const byte: u8 = try self.readByte(); // (Error || error{EndOfStream}) - if (byte == delimiter) return; - try writer.writeByte(byte); // @TypeOf(writer).Error - } - return error.StreamTooLong; - } else { - while (true) { - const byte: u8 = try self.readByte(); // (Error || error{EndOfStream}) - if (byte == delimiter) return; - try writer.writeByte(byte); // @TypeOf(writer).Error - } - // Can not throw `error.StreamTooLong` since there are no boundary. - } - } - - /// Reads from the stream until specified byte is found, discarding all data, - /// including the delimiter. - /// If end-of-stream is found, this function succeeds. - pub fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) Error!void { - while (true) { - const byte = self.readByte() catch |err| switch (err) { - error.EndOfStream => return, - else => |e| return e, - }; - if (byte == delimiter) return; - } - } - - /// Reads 1 byte from the stream or returns `error.EndOfStream`. - pub fn readByte(self: Self) (Error || error{EndOfStream})!u8 { - var result: [1]u8 = undefined; - const amt_read = try self.read(result[0..]); - if (amt_read < 1) return error.EndOfStream; - return result[0]; - } - - /// Same as `readByte` except the returned byte is signed. - pub fn readByteSigned(self: Self) (Error || error{EndOfStream})!i8 { - return @as(i8, @bitCast(try self.readByte())); - } - - /// Reads exactly `num_bytes` bytes and returns as an array. - /// `num_bytes` must be comptime-known - pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) (Error || error{EndOfStream})![num_bytes]u8 { - var bytes: [num_bytes]u8 = undefined; - try self.readNoEof(&bytes); - return bytes; - } - - /// Reads bytes until `bounded.len` is equal to `num_bytes`, - /// or the stream ends. - /// - /// * it is assumed that `num_bytes` will not exceed `bounded.capacity()` - pub fn readIntoBoundedBytes( - self: Self, - comptime num_bytes: usize, - bounded: *std.BoundedArray(u8, num_bytes), - ) Error!void { - while (bounded.len < num_bytes) { - // get at most the number of bytes free in the bounded array - const bytes_read = try self.read(bounded.unusedCapacitySlice()); - if (bytes_read == 0) return; - - // bytes_read will never be larger than @TypeOf(bounded.len) - // due to `self.read` being bounded by `bounded.unusedCapacitySlice()` - bounded.len += @as(@TypeOf(bounded.len), @intCast(bytes_read)); - } - } - - /// Reads at most `num_bytes` and returns as a bounded array. - pub fn readBoundedBytes(self: Self, comptime num_bytes: usize) Error!std.BoundedArray(u8, num_bytes) { - var result = std.BoundedArray(u8, num_bytes){}; - try self.readIntoBoundedBytes(num_bytes, &result); - return result; - } - - /// Reads a native-endian integer - pub fn readIntNative(self: Self, comptime T: type) (Error || error{EndOfStream})!T { - const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))); - return mem.readIntNative(T, &bytes); - } - - /// Reads a foreign-endian integer - pub fn readIntForeign(self: Self, comptime T: type) (Error || error{EndOfStream})!T { - const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))); - return mem.readIntForeign(T, &bytes); - } - - pub fn readIntLittle(self: Self, comptime T: type) !T { - const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))); - return mem.readIntLittle(T, &bytes); - } - - pub fn readIntBig(self: Self, comptime T: type) !T { - const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))); - return mem.readIntBig(T, &bytes); - } - - pub fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) !T { - const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))); - return mem.readInt(T, &bytes, endian); - } - - pub fn readVarInt(self: Self, comptime ReturnType: type, endian: std.builtin.Endian, size: usize) !ReturnType { - assert(size <= @sizeOf(ReturnType)); - var bytes_buf: [@sizeOf(ReturnType)]u8 = undefined; - const bytes = bytes_buf[0..size]; - try self.readNoEof(bytes); - return mem.readVarInt(ReturnType, bytes, endian); - } - - /// Optional parameters for `skipBytes` - pub const SkipBytesOptions = struct { - buf_size: usize = 512, - }; - - // `num_bytes` is a `u64` to match `off_t` - /// Reads `num_bytes` bytes from the stream and discards them - pub fn skipBytes(self: Self, num_bytes: u64, comptime options: SkipBytesOptions) !void { - var buf: [options.buf_size]u8 = undefined; - var remaining = num_bytes; - - while (remaining > 0) { - const amt = @min(remaining, options.buf_size); - try self.readNoEof(buf[0..amt]); - remaining -= amt; - } - } - - /// Reads `slice.len` bytes from the stream and returns if they are the same as the passed slice - pub fn isBytes(self: Self, slice: []const u8) !bool { - var i: usize = 0; - var matches = true; - while (i < slice.len) : (i += 1) { - if (slice[i] != try self.readByte()) { - matches = false; - } - } - return matches; - } - - pub fn readStruct(self: Self, comptime T: type) !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]; - } - - pub fn readStructBig(self: Self, comptime T: type) !T { - var res = try self.readStruct(T); - if (native_endian != std.builtin.Endian.Big) { - mem.byteSwapAllFields(T, &res); - } - return res; - } - - /// Reads an integer with the same size as the given enum's tag type. If the integer matches - /// an enum tag, casts the integer to the enum tag and returns it. Otherwise, returns an `error.InvalidValue`. - /// TODO optimization taking advantage of most fields being in order - pub fn readEnum(self: Self, comptime Enum: type, endian: std.builtin.Endian) !Enum { - const E = error{ - /// An integer was read, but it did not match any of the tags in the supplied enum. - InvalidValue, - }; - const type_info = @typeInfo(Enum).Enum; - const tag = try self.readInt(type_info.tag_type, endian); - - inline for (std.meta.fields(Enum)) |field| { - if (tag == field.value) { - return @field(Enum, field.name); - } - } - - return E.InvalidValue; - } - }; -} - -test "Reader" { - var buf = "a\x02".*; - var fis = std.io.fixedBufferStream(&buf); - const reader = fis.reader(); - try testing.expect((try reader.readByte()) == 'a'); - try testing.expect((try reader.readEnum(enum(u8) { - a = 0, - b = 99, - c = 2, - d = 3, - }, undefined)) == .c); - try testing.expectError(error.EndOfStream, reader.readByte()); -} - -test "Reader.isBytes" { - var fis = std.io.fixedBufferStream("foobar"); - const reader = fis.reader(); - try testing.expectEqual(true, try reader.isBytes("foo")); - try testing.expectEqual(false, try reader.isBytes("qux")); -} - -test "Reader.skipBytes" { - var fis = std.io.fixedBufferStream("foobar"); - const reader = fis.reader(); - try reader.skipBytes(3, .{}); - try testing.expect(try reader.isBytes("bar")); - try reader.skipBytes(0, .{}); - try testing.expectError(error.EndOfStream, reader.skipBytes(1, .{})); -} - -test "Reader.readUntilDelimiterArrayList returns ArrayLists with bytes read until the delimiter, then EndOfStream" { - const a = std.testing.allocator; - var list = std.ArrayList(u8).init(a); - defer list.deinit(); - - var fis = std.io.fixedBufferStream("0000\n1234\n"); - const reader = fis.reader(); - - try reader.readUntilDelimiterArrayList(&list, '\n', 5); - try std.testing.expectEqualStrings("0000", list.items); - try reader.readUntilDelimiterArrayList(&list, '\n', 5); - try std.testing.expectEqualStrings("1234", list.items); - try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5)); -} - -test "Reader.readUntilDelimiterArrayList returns an empty ArrayList" { - const a = std.testing.allocator; - var list = std.ArrayList(u8).init(a); - defer list.deinit(); - - var fis = std.io.fixedBufferStream("\n"); - const reader = fis.reader(); - - try reader.readUntilDelimiterArrayList(&list, '\n', 5); - try std.testing.expectEqualStrings("", list.items); -} - -test "Reader.readUntilDelimiterArrayList returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { - const a = std.testing.allocator; - var list = std.ArrayList(u8).init(a); - defer list.deinit(); - - var fis = std.io.fixedBufferStream("1234567\n"); - const reader = fis.reader(); - - try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterArrayList(&list, '\n', 5)); - try std.testing.expectEqualStrings("12345", list.items); - try reader.readUntilDelimiterArrayList(&list, '\n', 5); - try std.testing.expectEqualStrings("67", list.items); -} - -test "Reader.readUntilDelimiterArrayList returns EndOfStream" { - const a = std.testing.allocator; - var list = std.ArrayList(u8).init(a); - defer list.deinit(); - - var fis = std.io.fixedBufferStream("1234"); - const reader = fis.reader(); - - try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5)); - try std.testing.expectEqualStrings("1234", list.items); -} - -test "Reader.readUntilDelimiterAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" { - const a = std.testing.allocator; - - var fis = std.io.fixedBufferStream("0000\n1234\n"); - const reader = fis.reader(); - - { - var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); - defer a.free(result); - try std.testing.expectEqualStrings("0000", result); - } - - { - var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); - defer a.free(result); - try std.testing.expectEqualStrings("1234", result); - } - - try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5)); -} - -test "Reader.readUntilDelimiterAlloc returns an empty ArrayList" { - const a = std.testing.allocator; - - var fis = std.io.fixedBufferStream("\n"); - const reader = fis.reader(); - - { - var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); - defer a.free(result); - try std.testing.expectEqualStrings("", result); - } -} - -test "Reader.readUntilDelimiterAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { - const a = std.testing.allocator; - - var fis = std.io.fixedBufferStream("1234567\n"); - const reader = fis.reader(); - - try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterAlloc(a, '\n', 5)); - - var result = try reader.readUntilDelimiterAlloc(a, '\n', 5); - defer a.free(result); - try std.testing.expectEqualStrings("67", result); -} - -test "Reader.readUntilDelimiterAlloc returns EndOfStream" { - const a = std.testing.allocator; - - var fis = std.io.fixedBufferStream("1234"); - const reader = fis.reader(); - - try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5)); -} - -test "Reader.readUntilDelimiter returns bytes read until the delimiter" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("0000\n1234\n"); - const reader = fis.reader(); - try std.testing.expectEqualStrings("0000", try reader.readUntilDelimiter(&buf, '\n')); - try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n')); -} - -test "Reader.readUntilDelimiter returns an empty string" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("\n"); - const reader = fis.reader(); - try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n')); -} - -test "Reader.readUntilDelimiter returns StreamTooLong, then an empty string" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("12345\n"); - const reader = fis.reader(); - try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); - try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n')); -} - -test "Reader.readUntilDelimiter returns StreamTooLong, then bytes read until the delimiter" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("1234567\n"); - const reader = fis.reader(); - try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); - try std.testing.expectEqualStrings("67", try reader.readUntilDelimiter(&buf, '\n')); -} - -test "Reader.readUntilDelimiter returns EndOfStream" { - { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream(""); - const reader = fis.reader(); - try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); - } - { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("1234"); - const reader = fis.reader(); - try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); - } -} - -test "Reader.readUntilDelimiter returns bytes read until delimiter, then EndOfStream" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("1234\n"); - const reader = fis.reader(); - try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n')); - try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); -} - -test "Reader.readUntilDelimiter returns StreamTooLong, then EndOfStream" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("12345"); - const reader = fis.reader(); - try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); - try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); -} - -test "Reader.readUntilDelimiter writes all bytes read to the output buffer" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("0000\n12345"); - const reader = fis.reader(); - _ = try reader.readUntilDelimiter(&buf, '\n'); - try std.testing.expectEqualStrings("0000\n", &buf); - try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); - try std.testing.expectEqualStrings("12345", &buf); -} - -test "Reader.readUntilDelimiterOrEofAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" { - const a = std.testing.allocator; - - var fis = std.io.fixedBufferStream("0000\n1234\n"); - const reader = fis.reader(); - - { - var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; - defer a.free(result); - try std.testing.expectEqualStrings("0000", result); - } - - { - var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; - defer a.free(result); - try std.testing.expectEqualStrings("1234", result); - } - - try std.testing.expect((try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)) == null); -} - -test "Reader.readUntilDelimiterOrEofAlloc returns an empty ArrayList" { - const a = std.testing.allocator; - - var fis = std.io.fixedBufferStream("\n"); - const reader = fis.reader(); - - { - var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; - defer a.free(result); - try std.testing.expectEqualStrings("", result); - } -} - -test "Reader.readUntilDelimiterOrEofAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { - const a = std.testing.allocator; - - var fis = std.io.fixedBufferStream("1234567\n"); - const reader = fis.reader(); - - try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)); - - var result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; - defer a.free(result); - try std.testing.expectEqualStrings("67", result); -} - -test "Reader.readUntilDelimiterOrEof returns bytes read until the delimiter" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("0000\n1234\n"); - const reader = fis.reader(); - try std.testing.expectEqualStrings("0000", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); - try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); -} - -test "Reader.readUntilDelimiterOrEof returns an empty string" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("\n"); - const reader = fis.reader(); - try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); -} - -test "Reader.readUntilDelimiterOrEof returns StreamTooLong, then an empty string" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("12345\n"); - const reader = fis.reader(); - try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); - try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); -} - -test "Reader.readUntilDelimiterOrEof returns StreamTooLong, then bytes read until the delimiter" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("1234567\n"); - const reader = fis.reader(); - try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); - try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); -} - -test "Reader.readUntilDelimiterOrEof returns null" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream(""); - const reader = fis.reader(); - try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null); -} - -test "Reader.readUntilDelimiterOrEof returns bytes read until delimiter, then null" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("1234\n"); - const reader = fis.reader(); - try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); - try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null); -} - -test "Reader.readUntilDelimiterOrEof returns bytes read until end-of-stream" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("1234"); - const reader = fis.reader(); - try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); -} - -test "Reader.readUntilDelimiterOrEof returns StreamTooLong, then bytes read until end-of-stream" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("1234567"); - const reader = fis.reader(); - try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); - try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); -} - -test "Reader.readUntilDelimiterOrEof writes all bytes read to the output buffer" { - var buf: [5]u8 = undefined; - var fis = std.io.fixedBufferStream("0000\n12345"); - const reader = fis.reader(); - _ = try reader.readUntilDelimiterOrEof(&buf, '\n'); - try std.testing.expectEqualStrings("0000\n", &buf); - try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); - try std.testing.expectEqualStrings("12345", &buf); -} - -test "Reader.streamUntilDelimiter writes all bytes without delimiter to the output" { - const input_string = "some_string_with_delimiter!"; - var input_fbs = std.io.fixedBufferStream(input_string); - const reader = input_fbs.reader(); - - var output: [input_string.len]u8 = undefined; - var output_fbs = std.io.fixedBufferStream(&output); - const writer = output_fbs.writer(); - - try reader.streamUntilDelimiter(writer, '!', input_fbs.buffer.len); - try std.testing.expectEqualStrings("some_string_with_delimiter", output_fbs.getWritten()); - try std.testing.expectError(error.EndOfStream, reader.streamUntilDelimiter(writer, '!', input_fbs.buffer.len)); - - input_fbs.reset(); - output_fbs.reset(); - - try std.testing.expectError(error.StreamTooLong, reader.streamUntilDelimiter(writer, '!', 5)); -} - -test "Reader.readBoundedBytes correctly reads into a new bounded array" { - const test_string = "abcdefg"; - var fis = std.io.fixedBufferStream(test_string); - const reader = fis.reader(); - - var array = try reader.readBoundedBytes(10000); - try testing.expectEqualStrings(array.slice(), test_string); -} - -test "Reader.readIntoBoundedBytes correctly reads into a provided bounded array" { - const test_string = "abcdefg"; - var fis = std.io.fixedBufferStream(test_string); - const reader = fis.reader(); - - var bounded_array = std.BoundedArray(u8, 10000){}; - - // compile time error if the size is not the same at the provided `bounded.capacity()` - try reader.readIntoBoundedBytes(10000, &bounded_array); - try testing.expectEqualStrings(bounded_array.slice(), test_string); -} diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 5cd632b3a6..50f43a9507 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1853,7 +1853,7 @@ pub const data_in_code_entry = extern struct { pub const LoadCommandIterator = struct { ncmds: usize, - buffer: []align(@alignOf(u64)) const u8, + buffer: []const u8, index: usize = 0, pub const LoadCommand = struct { diff --git a/stage1/zig1.wasm b/stage1/zig1.wasm index c81de3122b..2084fd765d 100644 Binary files a/stage1/zig1.wasm and b/stage1/zig1.wasm differ