| ... | @@ -545,20 +545,76 @@ test fmtChar { | ... | @@ -545,20 +545,76 @@ test fmtChar { |
| 545 | /// | 545 | /// |
| 546 | /// Everything else is passed through unmodified. | 546 | /// Everything else is passed through unmodified. |
| 547 | pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void { | 547 | pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void { |
| | 548 | _ = try stringEscapeCounting(bytes, w); |
| | 549 | } |
| | 550 | |
| | 551 | pub fn stringEscapeCounting(bytes: []const u8, w: *Writer) Writer.Error!usize { |
| | 552 | var n: usize = 0; |
| 548 | for (bytes) |byte| switch (byte) { | 553 | for (bytes) |byte| switch (byte) { |
| 549 | '\t' => try w.writeAll("\\t"), | 554 | '\t' => { |
| 550 | '\n' => try w.writeAll("\\n"), | 555 | try w.writeAll("\\t"); |
| 551 | '\r' => try w.writeAll("\\r"), | 556 | n += 2; |
| 552 | '\\' => try w.writeAll("\\\\"), | 557 | }, |
| 553 | '"' => try w.writeAll("\\\""), | 558 | '\n' => { |
| | 559 | try w.writeAll("\\n"); |
| | 560 | n += 2; |
| | 561 | }, |
| | 562 | '\r' => { |
| | 563 | try w.writeAll("\\r"); |
| | 564 | n += 2; |
| | 565 | }, |
| | 566 | '\\' => { |
| | 567 | try w.writeAll("\\\\"); |
| | 568 | n += 2; |
| | 569 | }, |
| | 570 | '"' => { |
| | 571 | try w.writeAll("\\\""); |
| | 572 | n += 2; |
| | 573 | }, |
| 554 | 0...8, 11, 12, 14...0x1f, 0x7f => { | 574 | 0...8, 11, 12, 14...0x1f, 0x7f => { |
| 555 | try w.writeAll("\\x"); | 575 | try w.writeAll("\\x"); |
| 556 | try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }); | 576 | try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }); |
| | 577 | n += 4; |
| | 578 | }, |
| | 579 | else => { |
| | 580 | try w.writeByte(byte); |
| | 581 | n += 1; |
| 557 | }, | 582 | }, |
| 558 | else => try w.writeByte(byte), | | |
| 559 | }; | 583 | }; |
| | 584 | return n; |
| 560 | } | 585 | } |
| 561 | | 586 | |
| | 587 | pub const StringEscapeWriter = struct { |
| | 588 | out: *Writer, |
| | 589 | writer: Writer, |
| | 590 | |
| | 591 | pub fn init(out: *Writer, buffer: []u8) @This() { |
| | 592 | return .{ |
| | 593 | .out = out, |
| | 594 | .writer = .{ |
| | 595 | .vtable = &.{ .drain = @This().drain }, |
| | 596 | .buffer = buffer, |
| | 597 | }, |
| | 598 | }; |
| | 599 | } |
| | 600 | |
| | 601 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { |
| | 602 | const sew: *StringEscapeWriter = @alignCast(@fieldParentPtr("writer", w)); |
| | 603 | const out = sew.out; |
| | 604 | _ = try stringEscapeCounting(w.buffered(), out); |
| | 605 | w.end = 0; |
| | 606 | var n: usize = 0; |
| | 607 | for (data[0 .. data.len - 1]) |bytes| { |
| | 608 | n += try stringEscapeCounting(bytes, out); |
| | 609 | } |
| | 610 | const pattern = data[data.len - 1]; |
| | 611 | for (0..splat) |_| { |
| | 612 | n += try stringEscapeCounting(pattern, out); |
| | 613 | } |
| | 614 | return n; |
| | 615 | } |
| | 616 | }; |
| | 617 | |
| 562 | /// Print as escaped contents of a single-quoted string. | 618 | /// Print as escaped contents of a single-quoted string. |
| 563 | pub fn charEscape(codepoint: u21, w: *Writer) Writer.Error!void { | 619 | pub fn charEscape(codepoint: u21, w: *Writer) Writer.Error!void { |
| 564 | switch (codepoint) { | 620 | switch (codepoint) { |