hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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:
Mfmt/README | 4++--
Mfmt/fmt.ha | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mmath/ints.ha | 2+-
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;