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:
2026-06-13 10:36:11 +09:00
parent 69e31355ce
commit bccd111a16
2 changed files with 60 additions and 5 deletions

View File

@@ -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