lib/fmt: encode rune args as UTF-8, not a truncated low byte (#66)
writeone and formatraw's rune arm did `buf[0] = r: u8; putbytes(...,1)`, emitting only the low byte — invalid UTF-8 for any rune > 0x7F, with a success return. rawlen's rune arm hardcoded 1, desyncing width-padding for multibyte runes. Route both write arms through utf8.encoderune and rawlen through utf8.runesz (ref/hare/fmt/print.ha:84). Uses the current in-tree encoderune(out: []u8, r) caller-buffer signature; report-#168's signature realignment is a separate item, not folded here. fmttest gains fprintrune (1/2/3/4-byte runes) + fprintf_rune_width (multibyte pad).
This commit is contained in:
@@ -303,6 +303,46 @@ fn errsource() io.stream = {
|
||||
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
||||
};
|
||||
|
||||
// ---- fprint: rune args emit full UTF-8, not a truncated byte (#66) -----
|
||||
// writeone/formatraw's rune arm did `buf[0] = r: u8; putbytes(...,1)`,
|
||||
// emitting only the low byte (invalid UTF-8 for r > 0x7F). The fix routes
|
||||
// through utf8.encoderune (ref/hare/fmt/print.ha:84). Edge runes: é (2B),
|
||||
// € (3B), 😀 (4B), A (1B).
|
||||
|
||||
@test fn fprintrune() void = {
|
||||
let mem: memio.stream = memio.dynamic();
|
||||
let s: io.stream = &mem.vt;
|
||||
|
||||
let r: (size | io.error) = fmt.fprint(s, 233: rune, 0x20AC: rune,
|
||||
0x1F600: rune, 65: rune);
|
||||
match (r) {
|
||||
case let n: size => { assert(!(n: i32 != 13)); }; // 2+1+3+1+4+1+1
|
||||
case let eioe: io.error => abort();
|
||||
};
|
||||
assert(!(!streq(memio.string(&mem), "é € 😀 A")));
|
||||
|
||||
let c: (void | io.error) = io.close(s);
|
||||
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
||||
};
|
||||
|
||||
// rawlen's rune arm hardcoded 1, desyncing width-padding for a multibyte
|
||||
// rune. With runesz it counts the encoded length: € (3B) in width 5 pads
|
||||
// to " €" (2 spaces + 3 bytes = 5).
|
||||
@test fn fprintf_rune_width() void = {
|
||||
let mem: memio.stream = memio.dynamic();
|
||||
let s: io.stream = &mem.vt;
|
||||
|
||||
let r: (size | io.error) = fmt.fprintf(s, "{:5}", 0x20AC: rune);
|
||||
match (r) {
|
||||
case let n: size => { assert(!(n: i32 != 5)); };
|
||||
case let eioe: io.error => abort();
|
||||
};
|
||||
assert(!(!streq(memio.string(&mem), " €")));
|
||||
|
||||
let c: (void | io.error) = io.close(s);
|
||||
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
||||
};
|
||||
|
||||
@test fn fprintf_pad_underscore() void = {
|
||||
let mem: memio.stream = memio.dynamic();
|
||||
let s: io.stream = &mem.vt;
|
||||
|
||||
Reference in New Issue
Block a user