commit 3c01dc5be8e3f1879c719be229b840438df7ac07
parent 081a021cc699985aeaf6abeac94a9a01679368bd
Author: Gabriel Schmotzer <smocerg@gmail.com>
Date: Wed, 31 Aug 2022 18:48:12 +0200
fix fmt::fprintf's '+' and '0' modifiers
Fixes https://todo.sr.ht/~sircmpwn/hare/557
Signed-off-by: Gabriel Schmotzer <smocerg@gmail.com>
Diffstat:
3 files changed, 93 insertions(+), 22 deletions(-)
diff --git a/fmt/README b/fmt/README
@@ -15,14 +15,14 @@ You may use a colon to add format modifiers; for example, "{:x}" will format an
argument in hexadecimal, and "{3:-10}" will left-align the 4rd argument (zero
indexed) to at least 10 characters.
-The format modifiers takes the form of an optional flag character:
+The format modifiers take the form of optional flag characters:
- "0": Numeric values are zero-padded up to the required width.
- "-": The value shall be left-aligned, and spaces inserted on the right to meet the required width. "-" takes precedence over "0" if both are used.
- " ": (a space) insert a space before positive numbers, where "-" would be if it were negative.
- "+": insert a "+" before positive numbers, where "-" would be if it were negative. "+" takes precedence over " " if both are used.
-Following the flag, an optional decimal number shall specify the minimum width
+Following the flags, an optional decimal number shall specify the minimum width
of this field. If "0" or "-" were not given, the default behavior shall be to
pad with spaces to achieve the necessary width.
diff --git a/fmt/fmt.ha b/fmt/fmt.ha
@@ -9,6 +9,7 @@ use ascii;
use bufio;
use encoding::utf8;
use io;
+use math;
use os;
use strconv;
use strings;
@@ -292,10 +293,21 @@ fn format(
arg: formattable,
mod: *modifiers,
) (size | io::error) = {
- let z = format_raw(io::empty, arg, mod)?;
+ let z = 0z;
+ let reprlen = 0z;
+ if (mod.padding == padding::ALIGN_LEFT) {
+ reprlen = format_raw(out, arg, mod, 0, [])?;
+ z += reprlen;
+ } else {
+ reprlen = format_raw(io::empty, arg, mod, 0, [])?;
+ };
+
+ let padlen = 0z;
let pad: []u8 = [];
- if (z < mod.width: size) {
+
+ if (reprlen < mod.width: size) {
+ padlen = mod.width: size - reprlen;
pad = utf8::encoderune(switch (mod.padding) {
case padding::ZEROES =>
yield '0';
@@ -304,16 +316,18 @@ fn format(
});
};
- if (mod.padding == padding::ALIGN_LEFT) {
- format_raw(out, arg, mod)?;
- };
-
- for (z < mod.width: size) {
- z += io::write(out, pad)?;
- };
-
- if (mod.padding != padding::ALIGN_LEFT) {
- format_raw(out, arg, mod)?;
+ if (arg is types::numeric && mod.padding == padding::ZEROES) {
+ // if padlen != 0, then inner padding will be applied
+ z += format_raw(out, arg, mod, padlen, pad)?;
+ }
+ else {
+ for (let i = 0z; i < padlen) {
+ i += io::write(out, pad)?;
+ };
+ z += padlen;
+ if (mod.padding != padding::ALIGN_LEFT) {
+ z += format_raw(out, arg, mod, 0, [])?;
+ };
};
return z;
@@ -323,6 +337,8 @@ fn format_raw(
out: io::handle,
arg: formattable,
mod: *modifiers,
+ padlen: size,
+ pad: []u8,
) (size | io::error) = {
match (arg) {
case let s: str =>
@@ -333,27 +349,62 @@ fn format_raw(
return io::write(out,
strings::toutf8(if (b) "true" else "false"));
case let n: types::numeric =>
- const s = strconv::numerictosb(n, mod.base);
- return io::write(out, strings::toutf8(s));
+ const (s1, s2) = get_split_number_repr(n, mod);
+ let z = io::write(out, strings::toutf8(s1))?;
+ // apply inner padding if required
+ for (let i = 0z; i < padlen) {
+ i += io::write(out, pad)?;
+ };
+ z += padlen;
+ z += io::write(out, strings::toutf8(s2))?;
+ return z;
case let p: uintptr =>
const s = strconv::uptrtosb(p, mod.base);
return io::write(out, strings::toutf8(s));
case let v: nullable *void =>
match (v) {
case let v: *void =>
- let n = io::write(out, strings::toutf8("0x"))?;
+ let z = io::write(out, strings::toutf8("0x"))?;
const s = strconv::uptrtosb(v: uintptr,
strconv::base::HEX_LOWER);
- n += io::write(out, strings::toutf8(s))?;
- return n;
+ z += io::write(out, strings::toutf8(s))?;
+ return z;
case null =>
- return format(out, "(null)", mod);
+ return io::write(out, strings::toutf8("(null)"));
};
case void =>
return io::write(out, strings::toutf8("void"));
};
};
+fn get_split_number_repr(n: types::numeric, mod: *modifiers) (str, str) = {
+ const s = strconv::numerictosb(n, mod.base);
+ if (is_negative(n)) {
+ return (
+ strings::sub(s, 0, 1),
+ strings::sub(s, 1, strings::end)
+ );
+ } else {
+ const prefix = switch (mod.negation) {
+ case negation::PLUS => yield "+";
+ case negation::SPACE => yield " ";
+ case negation::NONE => yield "";
+ };
+ return (prefix, s);
+ };
+};
+
+fn is_negative(n: types::numeric) bool = {
+ match (n) {
+ case let i: types::signed =>
+ return math::signi(i) < 0;
+ case let u: types::unsigned =>
+ return false;
+ case let f: types::floating =>
+ return math::signf(f) < 0;
+ };
+};
+
fn scan_uint(iter: *strings::iterator) uint = {
let num: []u8 = [];
defer free(num);
@@ -551,12 +602,32 @@ fn scan_parametric_modifiers(iter: *strings::iterator, pi: *paramindex) void = {
"world", &modifiers { ... },
) == "|hello|world|hello|");
- assert(bsprintf(buf, "x: {:08x}", 0xBEEF) == "x: 0000beef");
assert(bsprintf(buf, "x: {:8X}", 0xBEEF) == "x: BEEF");
+ assert(bsprintf(buf, "x: {:8X}", -0xBEEF) == "x: -BEEF");
+ assert(bsprintf(buf, "x: {:+8X}", 0xBEEF) == "x: +BEEF");
+ assert(bsprintf(buf, "x: {: 8X}", 0xBEEF) == "x: BEEF");
+
+ assert(bsprintf(buf, "x: {:+ 8X}", 0xBEEF) == "x: +BEEF");
+
assert(bsprintf(buf, "x: {:-8X}", 0xBEEF) == "x: BEEF ");
+ assert(bsprintf(buf, "x: {:-8X}", -0xBEEF) == "x: -BEEF ");
+ assert(bsprintf(buf, "x: {:-+8X}", 0xBEEF) == "x: +BEEF ");
+ assert(bsprintf(buf, "x: {:- 8X}", 0xBEEF) == "x: BEEF ");
+
+ assert(bsprintf(buf, "x: {:08x}", 0xBEEF) == "x: 0000beef");
+ assert(bsprintf(buf, "x: {:08x}", -0xBEEF) == "x: -000beef");
+ assert(bsprintf(buf, "x: {:+08x}", 0xBEEF) == "x: +000beef");
+ assert(bsprintf(buf, "x: {: 08x}", 0xBEEF) == "x: 000beef");
+
+ assert(bsprintf(buf, "x: {:-08X}", 0xBEEF) == "x: BEEF ");
+
assert(bsprintf(buf, "x: {:o}", 0o755) == "x: 755");
assert(bsprintf(buf, "x: {:b}", 0b11011) == "x: 11011");
+ assert(bsprintf(buf, "x: {:8}", "hello") == "x: hello");
+ assert(bsprintf(buf, "x: {:-8}", "hello") == "x: hello ");
+ assert(bsprintf(buf, "x: {:08}", "hello") == "x: 000hello");
+
assert(bsprintf(buf, "{} {} {} {} {}", true, false, null, 'x', void)
== "true false (null) x void");
};
diff --git a/math/ints.ha b/math/ints.ha
@@ -136,7 +136,7 @@ export fn signi64(n: i64) i64 = {
};
// Return 1 if n is positive, -1 if it's negative and 0 if it's 0.
-export fn signi(n: types::integer) i64 = {
+export fn signi(n: types::signed) i64 = {
match (n) {
case let n: i8 =>
return signi8(n): i64;