authorgravatar for 28024277+tjog@users.noreply.github.comtjog <28024277+tjog@users.noreply.github.com> 2023-11-29 20:58:56+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-11-29 14:58:56-05:00
log22d7c7d2953360afb29ed2c60185bb0bba32cc30
tree9b9592e038e5fd68755cb8c100804afd2bb25385
parent2a322645331532e22def160677a345854f00b7e2
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

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

1 files changed, 158 insertions(+), 20 deletions(-)

lib/std/debug.zig+158-20
......@@ -1395,31 +1395,169 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void {
13951395 // TODO fstat and make sure that the file has the correct size
13961396
13971397 var buf: [mem.page_size]u8 = undefined;
1398 var line: usize = 1;
1399 var column: usize = 1;
1400 while (true) {
1401 const amt_read = try f.read(buf[0..]);
1402 const slice = buf[0..amt_read];
1403
1404 for (slice) |byte| {
1405 if (line == line_info.line) {
1406 switch (byte) {
1407 '\t' => try out_stream.writeByte(' '),
1408 else => try out_stream.writeByte(byte),
1409 }
1410 if (byte == '\n') {
1411 return;
1412 }
1398 var amt_read = try f.read(buf[0..]);
1399 const line_start = seek: {
1400 var current_line_start: usize = 0;
1401 var next_line: usize = 1;
1402 while (next_line != line_info.line) {
1403 const slice = buf[current_line_start..amt_read];
1404 if (mem.indexOfScalar(u8, slice, '\n')) |pos| {
1405 next_line += 1;
1406 if (pos == slice.len - 1) {
1407 amt_read = try f.read(buf[0..]);
1408 current_line_start = 0;
1409 } else current_line_start += pos + 1;
1410 } else if (amt_read < buf.len) {
1411 return error.EndOfFile;
1412 } else {
1413 amt_read = try f.read(buf[0..]);
1414 current_line_start = 0;
14131415 }
1414 if (byte == '\n') {
1415 line += 1;
1416 column = 1;
1416 }
1417 break :seek current_line_start;
1418 };
1419 const slice = buf[line_start..amt_read];
1420 if (mem.indexOfScalar(u8, slice, '\n')) |pos| {
1421 const line = slice[0 .. pos + 1];
1422 mem.replaceScalar(u8, line, '\t', ' ');
1423 return out_stream.writeAll(line);
1424 } else { // Line is the last inside the buffer, and requires another read to find delimiter. Alternatively the file ends.
1425 mem.replaceScalar(u8, slice, '\t', ' ');
1426 try out_stream.writeAll(slice);
1427 while (amt_read == buf.len) {
1428 amt_read = try f.read(buf[0..]);
1429 if (mem.indexOfScalar(u8, buf[0..amt_read], '\n')) |pos| {
1430 const line = buf[0 .. pos + 1];
1431 mem.replaceScalar(u8, line, '\t', ' ');
1432 return out_stream.writeAll(line);
14171433 } else {
1418 column += 1;
1434 const line = buf[0..amt_read];
1435 mem.replaceScalar(u8, line, '\t', ' ');
1436 try out_stream.writeAll(line);
14191437 }
14201438 }
1439 // Make sure printing last line of file inserts extra newline
1440 try out_stream.writeByte('\n');
1441 }
1442}
1443
1444test "printLineFromFileAnyOs" {
1445 var output = std.ArrayList(u8).init(std.testing.allocator);
1446 defer output.deinit();
1447 const output_stream = output.writer();
1448
1449 const allocator = std.testing.allocator;
1450 const join = std.fs.path.join;
1451 const expectError = std.testing.expectError;
1452 const expectEqualStrings = std.testing.expectEqualStrings;
1453
1454 var test_dir = std.testing.tmpDir(.{});
1455 defer test_dir.cleanup();
1456 // Relies on testing.tmpDir internals which is not ideal, but LineInfo requires paths.
1457 const test_dir_path = try join(allocator, &.{ "zig-cache", "tmp", test_dir.sub_path[0..] });
1458 defer allocator.free(test_dir_path);
1459
1460 // Cases
1461 {
1462 const path = try join(allocator, &.{ test_dir_path, "one_line.zig" });
1463 defer allocator.free(path);
1464 try test_dir.dir.writeFile("one_line.zig", "no new lines in this file, but one is printed anyway");
1465
1466 try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
1467
1468 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1469 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", output.items);
1470 output.clearRetainingCapacity();
1471 }
1472 {
1473 const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" });
1474 defer allocator.free(path);
1475 try test_dir.dir.writeFile("three_lines.zig",
1476 \\1
1477 \\2
1478 \\3
1479 );
1480
1481 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1482 try expectEqualStrings("1\n", output.items);
1483 output.clearRetainingCapacity();
1484
1485 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
1486 try expectEqualStrings("3\n", output.items);
1487 output.clearRetainingCapacity();
1488 }
1489 {
1490 const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{});
1491 defer file.close();
1492 const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });
1493 defer allocator.free(path);
1494
1495 const overlap = 10;
1496 var writer = file.writer();
1497 try writer.writeByteNTimes('a', mem.page_size - overlap);
1498 try writer.writeByte('\n');
1499 try writer.writeByteNTimes('a', overlap);
1500
1501 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
1502 try expectEqualStrings(("a" ** overlap) ++ "\n", output.items);
1503 output.clearRetainingCapacity();
1504 }
1505 {
1506 const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{});
1507 defer file.close();
1508 const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
1509 defer allocator.free(path);
1510
1511 var writer = file.writer();
1512 try writer.writeByteNTimes('a', mem.page_size);
1513
1514 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1515 try expectEqualStrings(("a" ** mem.page_size) ++ "\n", output.items);
1516 output.clearRetainingCapacity();
1517 }
1518 {
1519 const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{});
1520 defer file.close();
1521 const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
1522 defer allocator.free(path);
1523
1524 var writer = file.writer();
1525 try writer.writeByteNTimes('a', 3 * mem.page_size);
1526
1527 try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
1528
1529 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1530 try expectEqualStrings(("a" ** (3 * mem.page_size)) ++ "\n", output.items);
1531 output.clearRetainingCapacity();
1532
1533 try writer.writeAll("a\na");
1534
1535 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1536 try expectEqualStrings(("a" ** (3 * mem.page_size)) ++ "a\n", output.items);
1537 output.clearRetainingCapacity();
1538
1539 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
1540 try expectEqualStrings("a\n", output.items);
1541 output.clearRetainingCapacity();
1542 }
1543 {
1544 const file = try test_dir.dir.createFile("file_of_newlines.zig", .{});
1545 defer file.close();
1546 const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" });
1547 defer allocator.free(path);
1548
1549 var writer = file.writer();
1550 const real_file_start = 3 * mem.page_size;
1551 try writer.writeByteNTimes('\n', real_file_start);
1552 try writer.writeAll("abc\ndef");
1553
1554 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
1555 try expectEqualStrings("abc\n", output.items);
1556 output.clearRetainingCapacity();
14211557
1422 if (amt_read < buf.len) return error.EndOfFile;
1558 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
1559 try expectEqualStrings("def\n", output.items);
1560 output.clearRetainingCapacity();
14231561 }
14241562}
14251563