diff options
| author | tjog <28024277+tjog@users.noreply.github.com> | 2023-11-29 20:58:56 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-11-29 14:58:56 -0500 |
| commit | 22d7c7d2953360afb29ed2c60185bb0bba32cc30 (patch) | |
| tree | 9b9592e038e5fd68755cb8c100804afd2bb25385 /lib/std/debug.zig | |
| parent | 2a322645331532e22def160677a345854f00b7e2 (diff) | |
| download | zig-22d7c7d2953360afb29ed2c60185bb0bba32cc30.tar.gz zig-22d7c7d2953360afb29ed2c60185bb0bba32cc30.zip | |
std.debug: optimized printLineFromFileAnyOs (#18142)
* std.debug: optimized printLineFromFileAnyOs
Uses mem.indexOfScalar to speed line iteration instead of byte for byte.
Also prints the whole line in a single write (or up to a page size at a
time)
Closes #18099
* add test cases for printLineFromFileAnyOs
Diffstat (limited to 'lib/std/debug.zig')
| -rw-r--r-- | lib/std/debug.zig | 178 |
1 files changed, 158 insertions, 20 deletions
diff --git a/lib/std/debug.zig b/lib/std/debug.zig index fb654aebd9..961059ebfb 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1395,31 +1395,169 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void { // TODO fstat and make sure that the file has the correct size var buf: [mem.page_size]u8 = undefined; - var line: usize = 1; - var column: usize = 1; - while (true) { - const amt_read = try f.read(buf[0..]); - const slice = buf[0..amt_read]; - - for (slice) |byte| { - if (line == line_info.line) { - switch (byte) { - '\t' => try out_stream.writeByte(' '), - else => try out_stream.writeByte(byte), - } - if (byte == '\n') { - return; - } + var amt_read = try f.read(buf[0..]); + const line_start = seek: { + var current_line_start: usize = 0; + var next_line: usize = 1; + while (next_line != line_info.line) { + const slice = buf[current_line_start..amt_read]; + if (mem.indexOfScalar(u8, slice, '\n')) |pos| { + next_line += 1; + if (pos == slice.len - 1) { + amt_read = try f.read(buf[0..]); + current_line_start = 0; + } else current_line_start += pos + 1; + } else if (amt_read < buf.len) { + return error.EndOfFile; + } else { + amt_read = try f.read(buf[0..]); + current_line_start = 0; } - if (byte == '\n') { - line += 1; - column = 1; + } + break :seek current_line_start; + }; + const slice = buf[line_start..amt_read]; + if (mem.indexOfScalar(u8, slice, '\n')) |pos| { + const line = slice[0 .. pos + 1]; + mem.replaceScalar(u8, line, '\t', ' '); + return out_stream.writeAll(line); + } else { // Line is the last inside the buffer, and requires another read to find delimiter. Alternatively the file ends. + mem.replaceScalar(u8, slice, '\t', ' '); + try out_stream.writeAll(slice); + while (amt_read == buf.len) { + amt_read = try f.read(buf[0..]); + if (mem.indexOfScalar(u8, buf[0..amt_read], '\n')) |pos| { + const line = buf[0 .. pos + 1]; + mem.replaceScalar(u8, line, '\t', ' '); + return out_stream.writeAll(line); } else { - column += 1; + const line = buf[0..amt_read]; + mem.replaceScalar(u8, line, '\t', ' '); + try out_stream.writeAll(line); } } + // Make sure printing last line of file inserts extra newline + try out_stream.writeByte('\n'); + } +} + +test "printLineFromFileAnyOs" { + var output = std.ArrayList(u8).init(std.testing.allocator); + defer output.deinit(); + const output_stream = output.writer(); + + const allocator = std.testing.allocator; + const join = std.fs.path.join; + const expectError = std.testing.expectError; + const expectEqualStrings = std.testing.expectEqualStrings; + + var test_dir = std.testing.tmpDir(.{}); + defer test_dir.cleanup(); + // Relies on testing.tmpDir internals which is not ideal, but LineInfo requires paths. + const test_dir_path = try join(allocator, &.{ "zig-cache", "tmp", test_dir.sub_path[0..] }); + defer allocator.free(test_dir_path); + + // Cases + { + const path = try join(allocator, &.{ test_dir_path, "one_line.zig" }); + defer allocator.free(path); + try test_dir.dir.writeFile("one_line.zig", "no new lines in this file, but one is printed anyway"); + + try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); + + try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try expectEqualStrings("no new lines in this file, but one is printed anyway\n", output.items); + output.clearRetainingCapacity(); + } + { + const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" }); + defer allocator.free(path); + try test_dir.dir.writeFile("three_lines.zig", + \\1 + \\2 + \\3 + ); + + try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try expectEqualStrings("1\n", output.items); + output.clearRetainingCapacity(); + + try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 3, .column = 0 }); + try expectEqualStrings("3\n", output.items); + output.clearRetainingCapacity(); + } + { + const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{}); + defer file.close(); + const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); + defer allocator.free(path); + + const overlap = 10; + var writer = file.writer(); + try writer.writeByteNTimes('a', mem.page_size - overlap); + try writer.writeByte('\n'); + try writer.writeByteNTimes('a', overlap); + + try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); + try expectEqualStrings(("a" ** overlap) ++ "\n", output.items); + output.clearRetainingCapacity(); + } + { + const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{}); + defer file.close(); + const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); + defer allocator.free(path); + + var writer = file.writer(); + try writer.writeByteNTimes('a', mem.page_size); + + try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try expectEqualStrings(("a" ** mem.page_size) ++ "\n", output.items); + output.clearRetainingCapacity(); + } + { + const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{}); + defer file.close(); + const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); + defer allocator.free(path); + + var writer = file.writer(); + try writer.writeByteNTimes('a', 3 * mem.page_size); + + try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); + + try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try expectEqualStrings(("a" ** (3 * mem.page_size)) ++ "\n", output.items); + output.clearRetainingCapacity(); + + try writer.writeAll("a\na"); + + try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try expectEqualStrings(("a" ** (3 * mem.page_size)) ++ "a\n", output.items); + output.clearRetainingCapacity(); + + try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); + try expectEqualStrings("a\n", output.items); + output.clearRetainingCapacity(); + } + { + const file = try test_dir.dir.createFile("file_of_newlines.zig", .{}); + defer file.close(); + const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" }); + defer allocator.free(path); + + var writer = file.writer(); + const real_file_start = 3 * mem.page_size; + try writer.writeByteNTimes('\n', real_file_start); + try writer.writeAll("abc\ndef"); + + try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); + try expectEqualStrings("abc\n", output.items); + output.clearRetainingCapacity(); - if (amt_read < buf.len) return error.EndOfFile; + try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 }); + try expectEqualStrings("def\n", output.items); + output.clearRetainingCapacity(); } } |
