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:
@@ -22,6 +22,7 @@
|
||||
package fmt;
|
||||
|
||||
import io;
|
||||
import encoding.utf8;
|
||||
import math;
|
||||
import memio;
|
||||
import os;
|
||||
@@ -115,9 +116,15 @@ fn writeone(s: io.handle, a: formattable) (size | io.error) = {
|
||||
return putbytes(s, v.ptr, v.len);
|
||||
};
|
||||
case let r: rune => {
|
||||
// ref/hare/fmt/print.ha:84 io::write(out, utf8::encoderune(r)):
|
||||
// emit the full UTF-8 encoding, not the truncated low byte.
|
||||
let buf: [4]u8;
|
||||
buf[0] = r: u8;
|
||||
return putbytes(s, &buf[0], 1);
|
||||
let sl: []u8;
|
||||
sl.ptr = &buf[0];
|
||||
sl.len = 4;
|
||||
sl.cap = 4;
|
||||
let nn: i32 = utf8.encoderune(sl, r);
|
||||
return putbytes(s, &buf[0], nn);
|
||||
};
|
||||
case let v: f64 => {
|
||||
// strconv.f64tos static-buffer view consumed before next
|
||||
@@ -348,7 +355,10 @@ fn rawlen(arg: formattable, m: *mods) i32 = {
|
||||
case let v: i64 => return rawleni64(v, m);
|
||||
case let v: str => return rawlenstr(v, m);
|
||||
case let b: bool => { if (b) { return 4; }; return 5; };
|
||||
case let r: rune => return 1;
|
||||
// width-padding must count the rune's encoded byte length, else a
|
||||
// multibyte rune desyncs the pad (ref/hare/fmt/print.ha:84 emits the
|
||||
// full encoding; runesz is its length).
|
||||
case let r: rune => return utf8.runesz(r);
|
||||
case let v: f64 => return rawlenf64(v, m);
|
||||
case let v: int => return rawleni64(v: i64, m);
|
||||
case let v: uint => return rawlenu64(v: u64, m);
|
||||
@@ -412,9 +422,14 @@ fn formatraw(s: io.handle, arg: formattable, m: *mods) (size | io.error) = {
|
||||
return putbytes(s, v.ptr, v.len);
|
||||
};
|
||||
case let r: rune => {
|
||||
// ref/hare/fmt/print.ha:84: full UTF-8 encoding, not the low byte.
|
||||
let buf: [4]u8;
|
||||
buf[0] = r: u8;
|
||||
return putbytes(s, &buf[0], 1);
|
||||
let sl: []u8;
|
||||
sl.ptr = &buf[0];
|
||||
sl.len = 4;
|
||||
sl.cap = 4;
|
||||
let nn: i32 = utf8.encoderune(sl, r);
|
||||
return putbytes(s, &buf[0], nn);
|
||||
};
|
||||
case let v: f64 => {
|
||||
// strconv.f64tos prepends '-' for negative values; peel here so
|
||||
|
||||
@@ -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