authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-13 18:57:51-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:26-07:00
log1164d5ece5b12b573c6501c94b9ad9e326199ba9
treece3d85d2a8fa90930f93e0c97835e40bece10ce9
parent383afd19d73c7d98c8d1f7fe9d474b980517372e

tweak std.io.Writer and followups

remove std.fs.Dir.readFileAllocOptions, replace with more flexible API readFileIntoArrayList remove std.fs.File.readToEndAllocOptions, replace with more flexible API readIntoArrayList update std.fs.File to new reader/writer API add helper functions to std.io.Reader.Limit replace std.io.Writer.FileLen with std.io.Reader.Limit make offset a type rather than u64 so that it can distinguish between streaming read and positional read avoid an unnecessary allocation in std.zig.readSourceFileToEndAlloc when there is a UTF-16 little endian BOM.

8 files changed, 263 insertions(+), 210 deletions(-)

lib/compiler/std-docs.zig+23-13
...@@ -1,13 +1,12 @@...@@ -1,13 +1,12 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const std = @import("std");2const std = @import("std");
3const mem = std.mem;3const mem = std.mem;
4const io = std.io;
5const Allocator = std.mem.Allocator;4const Allocator = std.mem.Allocator;
6const assert = std.debug.assert;5const assert = std.debug.assert;
7const Cache = std.Build.Cache;6const Cache = std.Build.Cache;
87
9fn usage() noreturn {8fn usage() noreturn {
10 io.getStdOut().writeAll(9 std.fs.File.stdout().writeAll(
11 \\Usage: zig std [options]10 \\Usage: zig std [options]
12 \\11 \\
13 \\Options:12 \\Options:
...@@ -63,7 +62,7 @@ pub fn main() !void {...@@ -63,7 +62,7 @@ pub fn main() !void {
63 var http_server = try address.listen(.{});62 var http_server = try address.listen(.{});
64 const port = http_server.listen_address.in.getPort();63 const port = http_server.listen_address.in.getPort();
65 const url_with_newline = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port});64 const url_with_newline = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port});
66 std.io.getStdOut().writeAll(url_with_newline) catch {};65 std.fs.File.stdout().writeAll(url_with_newline) catch {};
67 if (should_open_browser) {66 if (should_open_browser) {
68 openBrowserTab(gpa, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| {67 openBrowserTab(gpa, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| {
69 std.log.err("unable to open browser: {s}", .{@errorName(err)});68 std.log.err("unable to open browser: {s}", .{@errorName(err)});
...@@ -155,18 +154,29 @@ fn serveDocsFile(...@@ -155,18 +154,29 @@ fn serveDocsFile(
155 name: []const u8,154 name: []const u8,
156 content_type: []const u8,155 content_type: []const u8,
157) !void {156) !void {
158 const gpa = context.gpa;157 // Open the file with every request so that the user can make changes to
159 // The desired API is actually sendfile, which will require enhancing std.http.Server.158 // the file and refresh the HTML page without restarting this server.
160 // We load the file with every request so that the user can make changes to the file159 var file = try context.lib_dir.openFile(name, .{});
161 // and refresh the HTML page without restarting this server.160 defer file.close();
162 const file_contents = try context.lib_dir.readFileAlloc(gpa, name, 10 * 1024 * 1024);161 const content_length = std.math.cast(usize, (try file.stat()).size) orelse return error.FileTooBig;
163 defer gpa.free(file_contents);162
164 try request.respond(file_contents, .{163 var send_buffer: [4000]u8 = undefined;
165 .extra_headers = &.{164 var response = request.respondStreaming(.{
166 .{ .name = "content-type", .value = content_type },165 .send_buffer = &send_buffer,
167 cache_control_header,166 .content_length = content_length,
167 .respond_options = .{
168 .extra_headers = &.{
169 .{ .name = "content-type", .value = content_type },
170 cache_control_header,
171 },
168 },172 },
169 });173 });
174
175 try response.writer().unbuffered().writeFileAll(file, .{
176 .offset = .zero,
177 .limit = .init(content_length),
178 });
179 try response.end();
170}180}
171181
172fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {182fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
lib/std/fs/Dir.zig+54-30
...@@ -1963,41 +1963,65 @@ pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {...@@ -1963,41 +1963,65 @@ pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
1963 return buffer[0..end_index];1963 return buffer[0..end_index];
1964}1964}
19651965
1966/// On success, caller owns returned buffer.1966/// Reads all the bytes from the named file. On success, caller owns returned
1967/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.1967/// buffer.
1968/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).1968pub fn readFileAlloc(
1969/// On WASI, `file_path` should be encoded as valid UTF-8.1969 dir: Dir,
1970/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.1970 /// On Windows, should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1971pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {1971 /// On WASI, should be encoded as valid UTF-8.
1972 return self.readFileAllocOptions(allocator, file_path, max_bytes, null, .of(u8), null);1972 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1973}1973 file_path: []const u8,
19741974 /// Used to allocate the result.
1975/// On success, caller owns returned buffer.1975 gpa: mem.Allocator,
1976/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.1976 /// If exceeded:
1977/// If `size_hint` is specified the initial buffer size is calculated using1977 /// * The array list's length is increased by exactly one byte past `limit`.
1978/// that value, otherwise the effective file size is used instead.1978 /// * The file seek position is advanced by exactly one byte past `limit`.
1979/// Allows specifying alignment and a sentinel value.1979 /// * `error.FileTooBig` is returned.
1980/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).1980 limit: std.io.Reader.Limit,
1981/// On WASI, `file_path` should be encoded as valid UTF-8.1981) (File.OpenError || File.ReadAllocError)![]u8 {
1982/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.1982 var buffer: std.ArrayListUnmanaged(u8) = .empty;
1983pub fn readFileAllocOptions(1983 defer buffer.deinit(gpa);
1984 self: Dir,1984 try readFileIntoArrayList(dir, file_path, gpa, limit, null, &buffer);
1985 allocator: mem.Allocator,1985 return buffer.toOwnedSlice(gpa);
1986}
1987
1988/// Reads all the bytes from the named file, appending them into the provided
1989/// array list.
1990///
1991/// If `limit` is exceeded:
1992/// * The array list's length is increased by exactly one byte past `limit`.
1993/// * The file seek position is advanced by exactly one byte past `limit`.
1994/// * `error.FileTooBig` is returned.
1995pub fn readFileIntoArrayList(
1996 dir: Dir,
1997 /// On Windows, should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1998 /// On WASI, should be encoded as valid UTF-8.
1999 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1986 file_path: []const u8,2000 file_path: []const u8,
1987 max_bytes: usize,2001 gpa: Allocator,
2002 limit: std.io.Reader.Limit,
2003 /// If specified, the initial buffer size is calculated using this value,
2004 /// otherwise the effective file size is used instead.
1988 size_hint: ?usize,2005 size_hint: ?usize,
1989 comptime alignment: std.mem.Alignment,2006 comptime alignment: ?std.mem.Alignment,
1990 comptime optional_sentinel: ?u8,2007 list: *std.ArrayListAligned(u8, alignment),
1991) !(if (optional_sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {2008) (File.OpenError || File.ReadAllocError)!void {
1992 var file = try self.openFile(file_path, .{});2009 var file = try dir.openFile(file_path, .{});
1993 defer file.close();2010 defer file.close();
19942011
1995 // If the file size doesn't fit a usize it'll be certainly greater than2012 // Apply size hint by adjusting the array list's capacity.
1996 // `max_bytes`2013 if (size_hint) |size| {
1997 const stat_size = size_hint orelse std.math.cast(usize, try file.getEndPos()) orelse2014 try list.ensureUnusedCapacity(gpa, size);
1998 return error.FileTooBig;2015 } else if (file.getEndPos()) |size| {
2016 // If the file size doesn't fit a usize it'll be certainly exceed the limit.
2017 try list.ensureUnusedCapacity(gpa, std.math.cast(usize, size) orelse return error.FileTooBig);
2018 } else |err| switch (err) {
2019 // Ignore most errors; size hint is only an optimization.
2020 error.Unseekable, error.Unexpected, error.AccessDenied, error.PermissionDenied => {},
2021 else => |e| return e,
2022 }
19992023
2000 return file.readToEndAllocOptions(allocator, max_bytes, stat_size, alignment, optional_sentinel);2024 try file.readIntoArrayList(gpa, limit, alignment, list);
2001}2025}
20022026
2003pub const DeleteTreeError = error{2027pub const DeleteTreeError = error{
lib/std/fs/File.zig+104-98
...@@ -1142,46 +1142,43 @@ pub fn updateTimes(...@@ -1142,46 +1142,43 @@ pub fn updateTimes(
1142 try posix.futimens(self.handle, &times);1142 try posix.futimens(self.handle, &times);
1143}1143}
11441144
1145/// Reads all the bytes from the current position to the end of the file.1145pub const ReadAllocError = ReadError || Allocator.Error || error{FileTooBig};
1146/// On success, caller owns returned buffer.
1147/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1148pub fn readToEndAlloc(self: File, allocator: Allocator, max_bytes: usize) ![]u8 {
1149 return self.readToEndAllocOptions(allocator, max_bytes, null, .of(u8), null);
1150}
11511146
1152/// Reads all the bytes from the current position to the end of the file.1147/// Reads all the bytes from the current position to the end of the file.
1148///
1153/// On success, caller owns returned buffer.1149/// On success, caller owns returned buffer.
1154/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.1150///
1155/// If `size_hint` is specified the initial buffer size is calculated using1151/// If `limit` is exceeded, returns `error.FileTooBig`.
1156/// that value, otherwise an arbitrary value is used instead.1152pub fn readToEndAlloc(file: File, gpa: Allocator, limit: std.io.Reader.Limit) ReadAllocError![]u8 {
1157/// Allows specifying alignment and a sentinel value.1153 var buffer: std.ArrayListUnmanaged(u8) = .empty;
1158pub fn readToEndAllocOptions(1154 defer buffer.deinit(gpa);
1159 self: File,1155 try buffer.ensureUnusedCapacity(gpa, std.heap.page_size_min);
1160 allocator: Allocator,1156 try readIntoArrayList(file, gpa, limit, null, &buffer);
1161 max_bytes: usize,1157 return buffer.toOwnedSlice(gpa);
1162 size_hint: ?usize,1158}
1163 comptime alignment: Alignment,
1164 comptime optional_sentinel: ?u8,
1165) !(if (optional_sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
1166 // If no size hint is provided fall back to the size=0 code path
1167 const size = size_hint orelse 0;
1168
1169 // The file size returned by stat is used as hint to set the buffer
1170 // size. If the reported size is zero, as it happens on Linux for files
1171 // in /proc, a small buffer is allocated instead.
1172 const initial_cap = @min((if (size > 0) size else 1024), max_bytes) + @intFromBool(optional_sentinel != null);
1173 var array_list = try std.ArrayListAligned(u8, alignment).initCapacity(allocator, initial_cap);
1174 defer array_list.deinit();
1175
1176 self.reader().readAllArrayListAligned(alignment, &array_list, max_bytes) catch |err| switch (err) {
1177 error.StreamTooLong => return error.FileTooBig,
1178 else => |e| return e,
1179 };
11801159
1181 if (optional_sentinel) |sentinel| {1160/// Reads all the bytes from the current position to the end of the file,
1182 return try array_list.toOwnedSliceSentinel(sentinel);1161/// appending them into the provided array list.
1183 } else {1162///
1184 return try array_list.toOwnedSlice();1163/// If `limit` is exceeded:
1164/// * The array list's length is increased by exactly one byte past `limit`.
1165/// * The file seek position is advanced by exactly one byte past `limit`.
1166/// * `error.FileTooBig` is returned.
1167pub fn readIntoArrayList(
1168 file: File,
1169 gpa: Allocator,
1170 limit: std.io.Reader.Limit,
1171 comptime alignment: ?std.mem.Alignment,
1172 list: *std.ArrayListAligned(u8, alignment),
1173) ReadAllocError!void {
1174 var remaining = limit;
1175 while (true) {
1176 try list.ensureUnusedCapacity(gpa, 1);
1177 const buffer = remaining.slice1(list.unusedCapacitySlice());
1178 const n = try read(file, buffer);
1179 if (n == 0) return;
1180 list.items.len += n;
1181 remaining = remaining.subtract(n) orelse return error.FileTooBig;
1185 }1182 }
1186}1183}
11871184
...@@ -1584,35 +1581,19 @@ fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) posix...@@ -1584,35 +1581,19 @@ fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) posix
1584pub fn reader(file: File) std.io.Reader {1581pub fn reader(file: File) std.io.Reader {
1585 return .{1582 return .{
1586 .context = handleToOpaque(file.handle),1583 .context = handleToOpaque(file.handle),
1587 .vtable = .{1584 .vtable = &.{
1588 .posRead = reader_posRead,1585 .read = streamRead,
1589 .posReadVec = reader_posReadVec,1586 .readv = streamReadVec,
1590 .streamRead = reader_streamRead,
1591 .streamReadVec = reader_streamReadVec,
1592 },
1593 };
1594}
1595
1596pub fn unseekableReader(file: File) std.io.Reader {
1597 return .{
1598 .context = handleToOpaque(file.handle),
1599 .vtable = .{
1600 .posRead = null,
1601 .posReadVec = null,
1602 .streamRead = reader_streamRead,
1603 .streamReadVec = reader_streamReadVec,
1604 },1587 },
1605 };1588 };
1606}1589}
16071590
1608pub fn unstreamableReader(file: File) std.io.Reader {1591pub fn positionalReader(file: File) std.io.PositionalReader {
1609 return .{1592 return .{
1610 .context = handleToOpaque(file.handle),1593 .context = handleToOpaque(file.handle),
1611 .vtable = .{1594 .vtable = &.{
1612 .posRead = reader_posRead,1595 .read = posRead,
1613 .posReadVec = reader_posReadVec,1596 .readv = posReadVec,
1614 .streamRead = null,
1615 .streamReadVec = null,
1616 },1597 },
1617 };1598 };
1618}1599}
...@@ -1621,8 +1602,8 @@ pub fn writer(file: File) std.io.Writer {...@@ -1621,8 +1602,8 @@ pub fn writer(file: File) std.io.Writer {
1621 return .{1602 return .{
1622 .context = handleToOpaque(file.handle),1603 .context = handleToOpaque(file.handle),
1623 .vtable = &.{1604 .vtable = &.{
1624 .writeSplat = writer_writeSplat,1605 .writeSplat = writeSplat,
1625 .writeFile = writer_writeFile,1606 .writeFile = writeFile,
1626 },1607 },
1627 };1608 };
1628}1609}
...@@ -1631,19 +1612,18 @@ pub fn writer(file: File) std.io.Writer {...@@ -1631,19 +1612,18 @@ pub fn writer(file: File) std.io.Writer {
1631/// vectors through the underlying write calls as possible.1612/// vectors through the underlying write calls as possible.
1632const max_buffers_len = 16;1613const max_buffers_len = 16;
16331614
1634pub fn reader_posRead(1615fn posRead(
1635 context: ?*anyopaque,1616 context: ?*anyopaque,
1636 bw: *std.io.BufferedWriter,1617 bw: *std.io.BufferedWriter,
1637 limit: std.io.Reader.Limit,1618 limit: std.io.Reader.Limit,
1638 offset: u64,1619 offset: u64,
1639) std.io.Reader.Result {1620) std.io.Reader.Result {
1640 const file = opaqueToHandle(context);1621 const file = opaqueToFile(context);
1641 const len: std.io.Writer.Len = if (limit.unwrap()) |l| .init(l) else .entire_file;1622 return bw.writeFile(file, .init(offset), limit, &.{}, 0);
1642 return writer.writeFile(bw, file, .init(offset), len, &.{}, 0);
1643}1623}
16441624
1645pub fn reader_posReadVec(context: *anyopaque, data: []const []u8, offset: u64) anyerror!std.io.Reader.Status {1625fn posReadVec(context: *anyopaque, data: []const []u8, offset: u64) anyerror!std.io.Reader.Status {
1646 const file = opaqueToHandle(context);1626 const file = opaqueToFile(context);
1647 const n = try file.preadv(data, offset);1627 const n = try file.preadv(data, offset);
1648 return .{1628 return .{
1649 .len = n,1629 .len = n,
...@@ -1651,35 +1631,57 @@ pub fn reader_posReadVec(context: *anyopaque, data: []const []u8, offset: u64) a...@@ -1651,35 +1631,57 @@ pub fn reader_posReadVec(context: *anyopaque, data: []const []u8, offset: u64) a
1651 };1631 };
1652}1632}
16531633
1654pub fn reader_streamRead(1634fn streamRead(
1655 context: ?*anyopaque,1635 context: ?*anyopaque,
1656 bw: *std.io.BufferedWriter,1636 bw: *std.io.BufferedWriter,
1657 limit: std.io.Reader.Limit,1637 limit: std.io.Reader.Limit,
1658) anyerror!std.io.Reader.Status {1638) anyerror!std.io.Reader.Status {
1659 const file = opaqueToHandle(context);1639 const file = opaqueToFile(context);
1660 const len: std.io.Writer.Len = if (limit.unwrap()) |l| .init(l) else .entire_file;1640 const n = try bw.writeFile(file, .none, limit, &.{}, 0);
1661 const n = try writer.writeFile(bw, file, .none, len, &.{}, 0);
1662 return .{1641 return .{
1663 .len = n,1642 .len = @intCast(n),
1664 .end = n == 0,1643 .end = n == 0,
1665 };1644 };
1666}1645}
16671646
1668pub fn reader_streamReadVec(context: ?*anyopaque, data: []const []u8) anyerror!std.io.Reader.Status {1647fn streamReadVec(context: ?*anyopaque, data: []const []u8) anyerror!std.io.Reader.Status {
1669 const file = opaqueToHandle(context);1648 const handle = opaqueToHandle(context);
1670 const n = try file.readv(data);1649
1671 return .{1650 if (is_windows) {
1672 .len = n,1651 // Unfortunately, `ReadFileScatter` cannot be used since it requires
1673 .end = n == 0,1652 // page alignment, so we are stuck using only the first slice.
1674 };1653 // Avoid empty slices to prevent false positive end detections.
1654 var i: usize = 0;
1655 while (true) : (i += 1) {
1656 if (i >= data.len) return .{};
1657 if (data[i].len > 0) break;
1658 }
1659 const n = try windows.ReadFile(handle, data[i], null);
1660 return .{ .len = n, .end = n == 0 };
1661 }
1662
1663 var iovecs: [max_buffers_len]std.posix.iovec = undefined;
1664 var iovecs_i: usize = 0;
1665 for (data) |d| {
1666 // Since the OS checks pointer address before length, we must omit
1667 // length-zero vectors.
1668 if (d.len == 0) continue;
1669 iovecs[iovecs_i] = .{ .base = d.ptr, .len = d.len };
1670 iovecs_i += 1;
1671 if (iovecs_i >= iovecs.len) break;
1672 }
1673 const send_vecs = iovecs[0..iovecs_i];
1674 if (send_vecs.len == 0) return .{}; // Prevent false positive end detection on empty `data`.
1675 const n = try posix.readv(handle, send_vecs);
1676 return .{ .len = @intCast(n), .end = n == 0 };
1675}1677}
16761678
1677pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize {1679fn writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1678 const file = opaqueToHandle(context);1680 const handle = opaqueToHandle(context);
1679 var splat_buffer: [256]u8 = undefined;1681 var splat_buffer: [256]u8 = undefined;
1680 if (is_windows) {1682 if (is_windows) {
1681 if (data.len == 1 and splat == 0) return 0;1683 if (data.len == 1 and splat == 0) return 0;
1682 return windows.WriteFile(file, data[0], null);1684 return windows.WriteFile(handle, data[0], null);
1683 }1685 }
1684 var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;1686 var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
1685 var len: usize = @min(iovecs.len, data.len);1687 var len: usize = @min(iovecs.len, data.len);
...@@ -1688,8 +1690,8 @@ pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat:...@@ -1688,8 +1690,8 @@ pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat:
1688 .len = d.len,1690 .len = d.len,
1689 };1691 };
1690 switch (splat) {1692 switch (splat) {
1691 0 => return std.posix.writev(file, iovecs[0 .. len - 1]),1693 0 => return std.posix.writev(handle, iovecs[0 .. len - 1]),
1692 1 => return std.posix.writev(file, iovecs[0..len]),1694 1 => return std.posix.writev(handle, iovecs[0..len]),
1693 else => {1695 else => {
1694 const pattern = data[data.len - 1];1696 const pattern = data[data.len - 1];
1695 if (pattern.len == 1) {1697 if (pattern.len == 1) {
...@@ -1707,38 +1709,38 @@ pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat:...@@ -1707,38 +1709,38 @@ pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat:
1707 iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };1709 iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
1708 len += 1;1710 len += 1;
1709 }1711 }
1710 return std.posix.writev(file, iovecs[0..len]);1712 return std.posix.writev(handle, iovecs[0..len]);
1711 }1713 }
1712 },1714 },
1713 }1715 }
1714 return std.posix.writev(file, iovecs[0..len]);1716 return std.posix.writev(handle, iovecs[0..len]);
1715}1717}
17161718
1717pub fn writer_writeFile(1719fn writeFile(
1718 context: ?*anyopaque,1720 context: ?*anyopaque,
1719 in_file: std.fs.File,1721 in_file: std.fs.File,
1720 in_offset: std.io.Writer.Offset,1722 in_offset: std.io.Writer.Offset,
1721 in_len: std.io.Writer.FileLen,1723 in_limit: std.io.Writer.Limit,
1722 headers_and_trailers: []const []const u8,1724 headers_and_trailers: []const []const u8,
1723 headers_len: usize,1725 headers_len: usize,
1724) anyerror!usize {1726) anyerror!usize {
1725 const out_fd = opaqueToHandle(context);1727 const out_fd = opaqueToHandle(context);
1726 const in_fd = in_file.handle;1728 const in_fd = in_file.handle;
1727 const len_int = switch (in_len) {1729 const len_int = switch (in_limit) {
1728 .zero => return writer_writeSplat(context, headers_and_trailers, 1),1730 .zero => return writeSplat(context, headers_and_trailers, 1),
1729 .entire_file => 0,1731 .none => 0,
1730 else => in_len.int(),1732 else => in_limit.toInt().?,
1731 };1733 };
1732 if (native_os == .linux) sf: {1734 if (native_os == .linux) sf: {
1733 // Linux sendfile does not support headers or trailers but it does1735 // Linux sendfile does not support headers or trailers but it does
1734 // support a streaming read from in_file.1736 // support a streaming read from in_file.
1735 if (headers_len > 0) return writer_writeSplat(context, headers_and_trailers[0..headers_len], 1);1737 if (headers_len > 0) return writeSplat(context, headers_and_trailers[0..headers_len], 1);
1736 const max_count = 0x7ffff000; // Avoid EINVAL.1738 const max_count = 0x7ffff000; // Avoid EINVAL.
1737 const smaller_len = if (len_int == 0) max_count else @min(len_int, max_count);1739 const smaller_len = if (len_int == 0) max_count else @min(len_int, max_count);
1738 var off: std.os.linux.off_t = undefined;1740 var off: std.os.linux.off_t = undefined;
1739 const off_ptr: ?*std.os.linux.off_t = if (in_offset.toInt()) |offset| b: {1741 const off_ptr: ?*std.os.linux.off_t = if (in_offset.toInt()) |offset| b: {
1740 off = std.math.cast(std.os.linux.off_t, offset) orelse1742 off = std.math.cast(std.os.linux.off_t, offset) orelse
1741 return writer_writeSplat(context, headers_and_trailers, 1);1743 return writeSplat(context, headers_and_trailers, 1);
1742 break :b &off;1744 break :b &off;
1743 } else null;1745 } else null;
1744 if (true) @panic("TODO");1746 if (true) @panic("TODO");
...@@ -1753,7 +1755,7 @@ pub fn writer_writeFile(...@@ -1753,7 +1755,7 @@ pub fn writer_writeFile(
1753 } else if (n == 0 and len_int == 0) {1755 } else if (n == 0 and len_int == 0) {
1754 // The caller wouldn't be able to tell that the file transfer is1756 // The caller wouldn't be able to tell that the file transfer is
1755 // done and would incorrectly repeat the same call.1757 // done and would incorrectly repeat the same call.
1756 return writer_writeSplat(context, headers_and_trailers, 1);1758 return writeSplat(context, headers_and_trailers, 1);
1757 }1759 }
1758 return n;1760 return n;
1759 }1761 }
...@@ -1770,7 +1772,7 @@ pub fn writer_writeFile(...@@ -1770,7 +1772,7 @@ pub fn writer_writeFile(
1770 error.FileDescriptorNotASocket,1772 error.FileDescriptorNotASocket,
1771 error.NetworkUnreachable,1773 error.NetworkUnreachable,
1772 error.NetworkSubsystemFailed,1774 error.NetworkSubsystemFailed,
1773 => return writeFileUnseekable(out_fd, in_fd, in_offset, in_len, headers_and_trailers, headers_len),1775 => return writeFileUnseekable(out_fd, in_fd, in_offset, in_limit, headers_and_trailers, headers_len),
17741776
1775 else => |e| return e,1777 else => |e| return e,
1776 };1778 };
...@@ -1780,14 +1782,14 @@ fn writeFileUnseekable(...@@ -1780,14 +1782,14 @@ fn writeFileUnseekable(
1780 out_fd: Handle,1782 out_fd: Handle,
1781 in_fd: Handle,1783 in_fd: Handle,
1782 in_offset: u64,1784 in_offset: u64,
1783 in_len: std.io.Writer.FileLen,1785 in_limit: std.io.Writer.Limit,
1784 headers_and_trailers: []const []const u8,1786 headers_and_trailers: []const []const u8,
1785 headers_len: usize,1787 headers_len: usize,
1786) anyerror!usize {1788) anyerror!usize {
1787 _ = out_fd;1789 _ = out_fd;
1788 _ = in_fd;1790 _ = in_fd;
1789 _ = in_offset;1791 _ = in_offset;
1790 _ = in_len;1792 _ = in_limit;
1791 _ = headers_and_trailers;1793 _ = headers_and_trailers;
1792 _ = headers_len;1794 _ = headers_len;
1793 @panic("TODO writeFileUnseekable");1795 @panic("TODO writeFileUnseekable");
...@@ -1809,6 +1811,10 @@ fn opaqueToHandle(userdata: ?*anyopaque) Handle {...@@ -1809,6 +1811,10 @@ fn opaqueToHandle(userdata: ?*anyopaque) Handle {
1809 };1811 };
1810}1812}
18111813
1814fn opaqueToFile(userdata: ?*anyopaque) File {
1815 return .{ .handle = opaqueToHandle(userdata) };
1816}
1817
1812pub const SeekableStream = io.SeekableStream(1818pub const SeekableStream = io.SeekableStream(
1813 File,1819 File,
1814 SeekError,1820 SeekError,
lib/std/io/BufferedReader.zig+2-2
...@@ -43,14 +43,14 @@ fn eof_writeFile(...@@ -43,14 +43,14 @@ fn eof_writeFile(
43 context: ?*anyopaque,43 context: ?*anyopaque,
44 file: std.fs.File,44 file: std.fs.File,
45 offset: std.io.Writer.Offset,45 offset: std.io.Writer.Offset,
46 len: std.io.Writer.FileLen,46 limit: std.io.Writer.Limit,
47 headers_and_trailers: []const []const u8,47 headers_and_trailers: []const []const u8,
48 headers_len: usize,48 headers_len: usize,
49) anyerror!usize {49) anyerror!usize {
50 _ = context;50 _ = context;
51 _ = file;51 _ = file;
52 _ = offset;52 _ = offset;
53 _ = len;53 _ = limit;
54 _ = headers_and_trailers;54 _ = headers_and_trailers;
55 _ = headers_len;55 _ = headers_len;
56 return error.NoSpaceLeft;56 return error.NoSpaceLeft;
lib/std/io/BufferedWriter.zig+15-15
...@@ -410,19 +410,19 @@ pub fn writeStructEndian(bw: *BufferedWriter, value: anytype, endian: std.builti...@@ -410,19 +410,19 @@ pub fn writeStructEndian(bw: *BufferedWriter, value: anytype, endian: std.builti
410pub fn writeFile(410pub fn writeFile(
411 bw: *BufferedWriter,411 bw: *BufferedWriter,
412 file: std.fs.File,412 file: std.fs.File,
413 offset: u64,413 offset: Writer.Offset,
414 len: Writer.FileLen,414 limit: Writer.Limit,
415 headers_and_trailers: []const []const u8,415 headers_and_trailers: []const []const u8,
416 headers_len: usize,416 headers_len: usize,
417) anyerror!usize {417) anyerror!usize {
418 return passthru_writeFile(bw, file, offset, len, headers_and_trailers, headers_len);418 return passthru_writeFile(bw, file, offset, limit, headers_and_trailers, headers_len);
419}419}
420420
421fn passthru_writeFile(421fn passthru_writeFile(
422 context: ?*anyopaque,422 context: ?*anyopaque,
423 file: std.fs.File,423 file: std.fs.File,
424 offset: u64,424 offset: Writer.Offset,
425 len: Writer.FileLen,425 limit: Writer.Limit,
426 headers_and_trailers: []const []const u8,426 headers_and_trailers: []const []const u8,
427 headers_len: usize,427 headers_len: usize,
428) anyerror!usize {428) anyerror!usize {
...@@ -430,7 +430,7 @@ fn passthru_writeFile(...@@ -430,7 +430,7 @@ fn passthru_writeFile(
430 const buffer = bw.buffer;430 const buffer = bw.buffer;
431 if (buffer.len == 0) return track(431 if (buffer.len == 0) return track(
432 &bw.count,432 &bw.count,
433 try bw.unbuffered_writer.writeFile(file, offset, len, headers_and_trailers, headers_len),433 try bw.unbuffered_writer.writeFile(file, offset, limit, headers_and_trailers, headers_len),
434 );434 );
435 const start_end = bw.end;435 const start_end = bw.end;
436 const headers = headers_and_trailers[0..headers_len];436 const headers = headers_and_trailers[0..headers_len];
...@@ -457,7 +457,7 @@ fn passthru_writeFile(...@@ -457,7 +457,7 @@ fn passthru_writeFile(
457 @memcpy(remaining_buffers_for_trailers[0..send_trailers_len], trailers[0..send_trailers_len]);457 @memcpy(remaining_buffers_for_trailers[0..send_trailers_len], trailers[0..send_trailers_len]);
458 const send_headers_len = 1 + buffers_len;458 const send_headers_len = 1 + buffers_len;
459 const send_buffers = buffers[0 .. send_headers_len + send_trailers_len];459 const send_buffers = buffers[0 .. send_headers_len + send_trailers_len];
460 const n = try bw.unbuffered_writer.writeFile(file, offset, len, send_buffers, send_headers_len);460 const n = try bw.unbuffered_writer.writeFile(file, offset, limit, send_buffers, send_headers_len);
461 if (n < end) {461 if (n < end) {
462 @branchHint(.unlikely);462 @branchHint(.unlikely);
463 const remainder = buffer[n..end];463 const remainder = buffer[n..end];
...@@ -487,7 +487,7 @@ fn passthru_writeFile(...@@ -487,7 +487,7 @@ fn passthru_writeFile(
487 @memcpy(remaining_buffers[0..send_trailers_len], trailers[0..send_trailers_len]);487 @memcpy(remaining_buffers[0..send_trailers_len], trailers[0..send_trailers_len]);
488 const send_headers_len = 1;488 const send_headers_len = 1;
489 const send_buffers = buffers[0 .. send_headers_len + send_trailers_len];489 const send_buffers = buffers[0 .. send_headers_len + send_trailers_len];
490 const n = try bw.unbuffered_writer.writeFile(file, offset, len, send_buffers, send_headers_len);490 const n = try bw.unbuffered_writer.writeFile(file, offset, limit, send_buffers, send_headers_len);
491 if (n < end) {491 if (n < end) {
492 @branchHint(.unlikely);492 @branchHint(.unlikely);
493 const remainder = buffer[n..end];493 const remainder = buffer[n..end];
...@@ -500,26 +500,26 @@ fn passthru_writeFile(...@@ -500,26 +500,26 @@ fn passthru_writeFile(
500}500}
501501
502pub const WriteFileOptions = struct {502pub const WriteFileOptions = struct {
503 offset: u64 = 0,503 offset: Writer.Offset = .none,
504 /// If the size of the source file is known, it is likely that passing the504 /// If the size of the source file is known, it is likely that passing the
505 /// size here will save one syscall.505 /// size here will save one syscall.
506 len: Writer.FileLen = .entire_file,506 limit: Writer.Limit = .none,
507 /// Headers and trailers must be passed together so that in case `len` is507 /// Headers and trailers must be passed together so that in case `len` is
508 /// zero, they can be forwarded directly to `Writer.VTable.writev`.508 /// zero, they can be forwarded directly to `Writer.VTable.writev`.
509 ///509 ///
510 /// The parameter is mutable because this function needs to mutate the510 /// The parameter is mutable because this function needs to mutate the
511 /// fields in order to handle partial writes from `Writer.VTable.writeFile`.511 /// fields in order to handle partial writes from `Writer.VTable.writeFile`.
512 headers_and_trailers: [][]const u8 = &.{},512 headers_and_trailers: [][]const u8 = &.{},
513 /// The number of trailers is inferred from `headers_and_trailers.len -513 /// The number of trailers is inferred from
514 /// headers_len`.514 /// `headers_and_trailers.len - headers_len`.
515 headers_len: usize = 0,515 headers_len: usize = 0,
516};516};
517517
518pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOptions) anyerror!void {518pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOptions) anyerror!void {
519 const headers_and_trailers = options.headers_and_trailers;519 const headers_and_trailers = options.headers_and_trailers;
520 const headers = headers_and_trailers[0..options.headers_len];520 const headers = headers_and_trailers[0..options.headers_len];
521 if (options.len == .zero) return writevAll(bw, headers_and_trailers);521 if (options.limit == .zero) return writevAll(bw, headers_and_trailers);
522 if (options.len == .entire_file) {522 if (options.limit == .none) {
523 // When reading the whole file, we cannot include the trailers in the523 // When reading the whole file, we cannot include the trailers in the
524 // call that reads from the file handle, because we have no way to524 // call that reads from the file handle, because we have no way to
525 // determine whether a partial write is past the end of the file or525 // determine whether a partial write is past the end of the file or
...@@ -540,7 +540,7 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp...@@ -540,7 +540,7 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp
540 offset += n;540 offset += n;
541 }541 }
542 } else {542 } else {
543 var len = options.len.int();543 var len = options.limit.toInt().?;
544 var i: usize = 0;544 var i: usize = 0;
545 var offset = options.offset;545 var offset = options.offset;
546 while (true) {546 while (true) {
lib/std/io/Reader.zig+36-2
...@@ -48,11 +48,45 @@ pub const Status = packed struct(usize) {...@@ -48,11 +48,45 @@ pub const Status = packed struct(usize) {
48};48};
4949
50pub const Limit = enum(usize) {50pub const Limit = enum(usize) {
51 zero = 0,
51 none = std.math.maxInt(usize),52 none = std.math.maxInt(usize),
52 _,53 _,
5354
54 pub fn min(l: Limit, int: usize) usize {55 /// `std.math.maxInt(usize)` is interpreted to mean "no limit".
55 return @min(int, @intFromEnum(l));56 pub fn init(n: usize) Limit {
57 return @enumFromInt(n);
58 }
59
60 pub fn min(l: Limit, n: usize) usize {
61 return @min(n, @intFromEnum(l));
62 }
63
64 pub fn slice(l: Limit, s: []u8) []u8 {
65 return s[0..min(l, s.len)];
66 }
67
68 pub fn toInt(l: Limit) ?usize {
69 return if (l == .none) null else @intFromEnum(l);
70 }
71
72 /// Reduces a slice to account for the limit, leaving room for one extra
73 /// byte above the limit, allowing for the use case of differentiating
74 /// between end-of-stream and reaching the limit.
75 pub fn slice1(l: Limit, non_empty_buffer: []u8) []u8 {
76 assert(non_empty_buffer.len >= 1);
77 return non_empty_buffer[0..@min(@intFromEnum(l) +| 1, non_empty_buffer.len)];
78 }
79
80 pub fn nonzero(l: Limit) bool {
81 return @intFromEnum(l) > 0;
82 }
83
84 /// Return a new limit reduced by `amount` or return `null` indicating
85 /// limit would be exceeded.
86 pub fn subtract(l: Limit, amount: usize) ?Limit {
87 if (l == .none) return .{ .next = .none };
88 if (amount > @intFromEnum(l)) return null;
89 return @enumFromInt(@intFromEnum(l) - amount);
56 }90 }
57};91};
5892
lib/std/io/Writer.zig+17-29
...@@ -31,9 +31,11 @@ pub const VTable = struct {...@@ -31,9 +31,11 @@ pub const VTable = struct {
31 writeFile: *const fn (31 writeFile: *const fn (
32 ctx: ?*anyopaque,32 ctx: ?*anyopaque,
33 file: std.fs.File,33 file: std.fs.File,
34 /// If this is `none`, `file` will be streamed. Otherwise, it will be
35 /// read positionally without affecting the seek position.
34 offset: Offset,36 offset: Offset,
35 /// When zero, it means copy until the end of the file is reached.37 /// Maximum amount of bytes to read from the file.
36 len: FileLen,38 limit: Limit,
37 /// Headers and trailers must be passed together so that in case `len` is39 /// Headers and trailers must be passed together so that in case `len` is
38 /// zero, they can be forwarded directly to `VTable.writev`.40 /// zero, they can be forwarded directly to `VTable.writev`.
39 headers_and_trailers: []const []const u8,41 headers_and_trailers: []const []const u8,
...@@ -41,7 +43,10 @@ pub const VTable = struct {...@@ -41,7 +43,10 @@ pub const VTable = struct {
41 ) anyerror!usize,43 ) anyerror!usize,
42};44};
4345
46pub const Limit = std.io.Reader.Limit;
47
44pub const Offset = enum(u64) {48pub const Offset = enum(u64) {
49 zero = 0,
45 /// Indicates to read the file as a stream.50 /// Indicates to read the file as a stream.
46 none = std.math.maxInt(u64),51 none = std.math.maxInt(u64),
47 _,52 _,
...@@ -53,24 +58,7 @@ pub const Offset = enum(u64) {...@@ -53,24 +58,7 @@ pub const Offset = enum(u64) {
53 }58 }
5459
55 pub fn toInt(o: Offset) ?u64 {60 pub fn toInt(o: Offset) ?u64 {
56 if (o == .none) return null;61 return if (o == .none) null else @intFromEnum(o);
57 return @intFromEnum(o);
58 }
59};
60
61pub const FileLen = enum(u64) {
62 zero = 0,
63 entire_file = std.math.maxInt(u64),
64 _,
65
66 pub fn init(integer: u64) FileLen {
67 const result: FileLen = @enumFromInt(integer);
68 assert(result != .entire_file);
69 return result;
70 }
71
72 pub fn int(len: FileLen) u64 {
73 return @intFromEnum(len);
74 }62 }
75};63};
7664
...@@ -85,26 +73,26 @@ pub fn writeSplat(w: Writer, data: []const []const u8, splat: usize) anyerror!us...@@ -85,26 +73,26 @@ pub fn writeSplat(w: Writer, data: []const []const u8, splat: usize) anyerror!us
85pub fn writeFile(73pub fn writeFile(
86 w: Writer,74 w: Writer,
87 file: std.fs.File,75 file: std.fs.File,
88 offset: u64,76 offset: Offset,
89 len: FileLen,77 limit: Limit,
90 headers_and_trailers: []const []const u8,78 headers_and_trailers: []const []const u8,
91 headers_len: usize,79 headers_len: usize,
92) anyerror!usize {80) anyerror!usize {
93 return w.vtable.writeFile(w.context, file, offset, len, headers_and_trailers, headers_len);81 return w.vtable.writeFile(w.context, file, offset, limit, headers_and_trailers, headers_len);
94}82}
9583
96pub fn unimplemented_writeFile(84pub fn unimplemented_writeFile(
97 context: ?*anyopaque,85 context: ?*anyopaque,
98 file: std.fs.File,86 file: std.fs.File,
99 offset: Offset,87 offset: Offset,
100 len: FileLen,88 limit: Limit,
101 headers_and_trailers: []const []const u8,89 headers_and_trailers: []const []const u8,
102 headers_len: usize,90 headers_len: usize,
103) anyerror!usize {91) anyerror!usize {
104 _ = context;92 _ = context;
105 _ = file;93 _ = file;
106 _ = offset;94 _ = offset;
107 _ = len;95 _ = limit;
108 _ = headers_and_trailers;96 _ = headers_and_trailers;
109 _ = headers_len;97 _ = headers_len;
110 return error.Unimplemented;98 return error.Unimplemented;
...@@ -143,7 +131,7 @@ fn null_writeFile(...@@ -143,7 +131,7 @@ fn null_writeFile(
143 context: ?*anyopaque,131 context: ?*anyopaque,
144 file: std.fs.File,132 file: std.fs.File,
145 offset: Offset,133 offset: Offset,
146 len: FileLen,134 limit: Limit,
147 headers_and_trailers: []const []const u8,135 headers_and_trailers: []const []const u8,
148 headers_len: usize,136 headers_len: usize,
149) anyerror!usize {137) anyerror!usize {
...@@ -152,7 +140,7 @@ fn null_writeFile(...@@ -152,7 +140,7 @@ fn null_writeFile(
152 if (offset == .none) {140 if (offset == .none) {
153 @panic("TODO seek the file forwards");141 @panic("TODO seek the file forwards");
154 }142 }
155 if (len == .entire_file) {143 const limit_int = limit.toInt() orelse {
156 const headers = headers_and_trailers[0..headers_len];144 const headers = headers_and_trailers[0..headers_len];
157 for (headers) |bytes| n += bytes.len;145 for (headers) |bytes| n += bytes.len;
158 if (offset.toInt()) |off| {146 if (offset.toInt()) |off| {
...@@ -162,9 +150,9 @@ fn null_writeFile(...@@ -162,9 +150,9 @@ fn null_writeFile(
162 return n;150 return n;
163 }151 }
164 @panic("TODO stream from file until eof, counting");152 @panic("TODO stream from file until eof, counting");
165 }153 };
166 for (headers_and_trailers) |bytes| n += bytes.len;154 for (headers_and_trailers) |bytes| n += bytes.len;
167 return len.int() + n;155 return limit_int + n;
168}156}
169157
170test @"null" {158test @"null" {
lib/std/zig.zig+12-21
...@@ -543,20 +543,18 @@ test isUnderscore {...@@ -543,20 +543,18 @@ test isUnderscore {
543 try std.testing.expect(!isUnderscore("\\x5f"));543 try std.testing.expect(!isUnderscore("\\x5f"));
544}544}
545545
546pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: ?usize) ![:0]u8 {546pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: usize) ![:0]u8 {
547 const source_code = input.readToEndAllocOptions(547 var buffer: std.ArrayListAlignedUnmanaged(u8, .@"2") = .empty;
548 gpa,548 defer buffer.deinit(gpa);
549 max_src_size,549
550 size_hint,550 try buffer.ensureUnusedCapacity(size_hint);
551 .of(u8),551
552 0,552 input.readIntoArrayList(gpa, .init(max_src_size), .@"2", &buffer) catch |err| switch (err) {
553 ) catch |err| switch (err) {
554 error.ConnectionResetByPeer => unreachable,553 error.ConnectionResetByPeer => unreachable,
555 error.ConnectionTimedOut => unreachable,554 error.ConnectionTimedOut => unreachable,
556 error.NotOpenForReading => unreachable,555 error.NotOpenForReading => unreachable,
557 else => |e| return e,556 else => |e| return e,
558 };557 };
559 errdefer gpa.free(source_code);
560558
561 // Detect unsupported file types with their Byte Order Mark559 // Detect unsupported file types with their Byte Order Mark
562 const unsupported_boms = [_][]const u8{560 const unsupported_boms = [_][]const u8{
...@@ -565,30 +563,23 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: ?...@@ -565,30 +563,23 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: ?
565 "\xfe\xff", // UTF-16 big endian563 "\xfe\xff", // UTF-16 big endian
566 };564 };
567 for (unsupported_boms) |bom| {565 for (unsupported_boms) |bom| {
568 if (std.mem.startsWith(u8, source_code, bom)) {566 if (std.mem.startsWith(u8, buffer.items, bom)) {
569 return error.UnsupportedEncoding;567 return error.UnsupportedEncoding;
570 }568 }
571 }569 }
572570
573 // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8571 // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8
574 if (std.mem.startsWith(u8, source_code, "\xff\xfe")) {572 if (std.mem.startsWith(u8, buffer.items, "\xff\xfe")) {
575 if (source_code.len % 2 != 0) return error.InvalidEncoding;573 if (buffer.items.len % 2 != 0) return error.InvalidEncoding;
576 // TODO: after wrangle-writer-buffering branch is merged,574 return std.unicode.utf16LeToUtf8AllocZ(gpa, buffer.items) catch |err| switch (err) {
577 // avoid this unnecessary allocation
578 const aligned_copy = try gpa.alloc(u16, source_code.len / 2);
579 defer gpa.free(aligned_copy);
580 @memcpy(std.mem.sliceAsBytes(aligned_copy), source_code);
581 const source_code_utf8 = std.unicode.utf16LeToUtf8AllocZ(gpa, aligned_copy) catch |err| switch (err) {
582 error.DanglingSurrogateHalf => error.UnsupportedEncoding,575 error.DanglingSurrogateHalf => error.UnsupportedEncoding,
583 error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,576 error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,
584 error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,577 error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,
585 else => |e| return e,578 else => |e| return e,
586 };579 };
587 gpa.free(source_code);
588 return source_code_utf8;
589 }580 }
590581
591 return source_code;582 return buffer.toOwnedSliceSentinel(0);
592}583}
593584
594pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void {585pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void {