commit d09ea3b720bda1a31ba7bc7aa4095644ba271e6d
parent 5485a198ec9a4136ab988ce509b539e0eb60219d
Author: Ember Sawady <ecs@d2evs.net>
Date: Mon, 6 Nov 2023 21:07:59 +0000
fmt: factor out format string iterator
also implements precision handling, and replaces {:042} with {:.42}.
simplifies the code and prepares us for fmt::scan*
Signed-off-by: Ember Sawady <ecs@d2evs.net>
Diffstat:
19 files changed, 567 insertions(+), 689 deletions(-)
diff --git a/crypto/bcrypt/bcrypt.ha b/crypto/bcrypt/bcrypt.ha
@@ -82,7 +82,7 @@ fn mkhash(h: *hash) []u8 = {
if (h.minor != 0) {
fmt::fprintf(&buf, "{}", h.minor: rune)!;
};
- fmt::fprintf(&buf, "${:02}$", h.cost)!;
+ fmt::fprintf(&buf, "${:.2}$", h.cost)!;
io::write(&buf, h.salt)!;
io::write(&buf, h.hash)!;
return memio::buffer(&buf);
diff --git a/crypto/blake2b/+test.ha b/crypto/blake2b/+test.ha
@@ -58,7 +58,7 @@ use strings;
defer io::close(&hex)!;
for (let j = 0z; j < len(sum); j += 1) {
- fmt::fprintf(&hex, "{:02x}", sum[j])!;
+ fmt::fprintf(&hex, "{:.2x}", sum[j])!;
};
if (memio::string(&hex)! != vector.1) {
diff --git a/crypto/curve25519/+test.ha b/crypto/curve25519/+test.ha
@@ -233,7 +233,7 @@ use io;
fn printvector(name: str, vec: const *[SCALARSZ]u8) void = {
fmt::errorf("{:6} = [", name)!;
for (let i = 0z; i < 31; i += 1) {
- fmt::errorf("{:02x}, ", vec[i])!;
+ fmt::errorf("{:.2x}, ", vec[i])!;
};
- fmt::errorfln("{:02x}]", vec[31])!;
+ fmt::errorfln("{:.2x}]", vec[31])!;
};
diff --git a/encoding/hex/hex.ha b/encoding/hex/hex.ha
@@ -199,12 +199,12 @@ export fn dump(out: io::handle, data: []u8) (void | io::error) = {
let datalen = len(data): u32;
for (let off = 0u32; off < datalen; off += 16) {
- fmt::fprintf(out, "{:08x} ", off)?;
+ fmt::fprintf(out, "{:.8x} ", off)?;
let toff = 0z;
for (let i = 0z; i < 16 && off + i < datalen; i += 1) {
let val = data[off + i];
- toff += fmt::fprintf(out, "{}{:02x} ",
+ toff += fmt::fprintf(out, "{}{:.2x} ",
if (i == 8) " " else "", val)?;
};
diff --git a/fmt/+test.ha b/fmt/+test.ha
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use strconv;
+
+@test fn print() void = {
+ let buf: [1024]u8 = [0...];
+
+ assert(bsprint(buf, "hello world") == "hello world");
+ assert(bsprintf(buf, "hello world") == "hello world");
+ assert(bsprintf(buf, "{} {}", "hello", "world") == "hello world");
+ assert(bsprintf(buf, "{0} {1}", "hello", "world") == "hello world");
+ assert(bsprintf(buf, "{0} {0}", "hello", "world") == "hello hello");
+ assert(bsprintf(buf, "{1} {0} {1}", "hello", "world") == "world hello world");
+
+ const mod = &mods { width = 7, pad = ' ', ... };
+ assert(bsprintf(buf, "{%}", "hello", mod) == " hello");
+ assert(bsprintf(buf, "{%1}", "hello", mod) == " hello");
+ assert(bsprintf(buf, "{0%1}", "hello", mod) == " hello");
+ assert(bsprintf(buf, "{0%2}", "hello", 0, mod) == " hello");
+ assert(bsprintf(buf, "{1%2}", 0, "hello", mod) == " hello");
+ assert(bsprintf(buf, "{2%0}", mod, 0, "hello") == " hello");
+ assert(bsprintf(buf, "{2%}", mod, 0, "hello") == " hello");
+ assert(bsprintf(buf, "|{1%}|{}|", mod, "hello") == "| hello|hello|");
+ assert(bsprintf(buf, "|{}|{2%}|", "hello", mod, "world") == "|hello| world|");
+ assert(bsprintf(buf, "|{%}|{%}|{%}|{%}|",
+ "hello", &mods { ... },
+ "world", &mods { width = 10, pad = ' ', ... },
+ 123, &mods { prec = 10, ... },
+ 0xBEEF, &mods { base = strconv::base::HEX, ... },
+ ) == "|hello| world|0000000123|BEEF|");
+ assert(bsprintf(buf, "|{%}|{%}|{0%1}|",
+ "hello", &mods { ... },
+ "world", &mods { ... },
+ ) == "|hello|world|hello|");
+
+ 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: {:- 8X}", 0xBEEF) == "x: BEEF ");
+
+ assert(bsprintf(buf, "x: {:.8x}", 0xBEEF) == "x: 0000beef");
+ assert(bsprintf(buf, "x: {:.8x}", -0xBEEF) == "x: -000beef");
+ assert(bsprintf(buf, "x: {:+.8x}", 0xBEEF) == "x: +000beef");
+ assert(bsprintf(buf, "x: {: .8x}", 0xBEEF) == "x: 000beef");
+ assert(bsprintf(buf, "x: {:-_08X}", 0xBEEF) == "x: BEEF0000");
+
+ 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, "{:.5}", "hello world") == "hello");
+ assert(bsprintf(buf, "{:.5}", "hi") == "hi");
+ assert(bsprintf(buf, "{:5.2}", "hello") == " he");
+
+ assert(bsprintf(buf, "{:.1}", 123.0) == "100");
+ assert(bsprintf(buf, "{:.5}", 123.0) == "123");
+
+ assert(bsprintf(buf, "{} {} {} {} {}", true, false, null, 'x', void)
+ == "true false (null) x void");
+};
diff --git a/fmt/README b/fmt/README
@@ -3,38 +3,42 @@ verbatim, and format sequences, which describe how to format arguments from a
set of variadic parameters for printing.
A format sequence is enclosed in curly braces "{}". An empty sequence takes the
-next argument from the parameter list, in order. A specific parameter may be
+next argument from the parameter list, in order. A specific parameter can be
selected by indexing it from zero: "{0}", "{1}", and so on. To print "{", use
"{{", and for "}", use "}}".
-There are two ways to specify how an argument shall be formatted: inline format
+There are two ways to specify how an argument will be formatted: inline format
modifiers, and parametric format modifiers.
Inline format modifiers are a series of characters within a format sequence.
-You may use a colon to add format modifiers; for example, "{:x}" will format an
+You can use a colon to add format modifiers; for example, "{:x}" will format an
argument in hexadecimal, and "{3:-10}" will left-align the 4th argument (zero
indexed) to at least 10 characters.
-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 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.
-
-Following the width, an optional precision may be given as a decimal number
-following a "." character. For integer types, this gives the minimum number of
-digits to include. For floating types, this gives the number of digits following
-the radix to include.
-
-Following the precision, an optional character controls the output format:
-
-- x, X: print in lowercase or uppercase hexadecimal
-- o, b: print in octal or binary
+Format modifiers can be written in any order, and can also be repeated. If a
+modifier is repeated (or two conflicting modifiers are given, such as both "x"
+and "X") the one furthest to the right will be used.
+
+A format modifier can be any of the following:
+- A number N: Sets the width to N. If the value would otherwise be shorter than
+ N runes, insert padding characters in order to make it N runes long. By
+ default, the value is right-aligned, with padding inserted on the left side,
+ and the padding character is " " (a space).
+- "-": Left-align the value, inserting padding characters inserted on the right
+ side of the value in order to meet the width requirement.
+- "_" followed by a rune: Use the given rune as the padding character rather
+ than the default of " " (a space).
+- " " (a space): Insert a space before positive integers, where "-" would be if
+ it were negative.
+- "+": Insert a "+" before positive integers.
+- "x": Format numbers in lowercase hexadecimal.
+- "X": Format numbers in uppercase hexadecimal.
+- "o": Format numbers in octal.
+- "b": Format numbers in binary.
+- "." followed by a number N: Sets the precision to N. Integers will be
+ left-padded with "0"s between the sign and the number itself. Strings
+ will be truncated to N runes. Floats will only include up to N digits after
+ the decimal point.
Some inline modifier examples:
@@ -43,11 +47,11 @@ Some inline modifier examples:
fmt::printf("{:x} {:X}", 51966, 61453); // "cafe F00D"
fmt::printf("{:-5}", 42); // "42 "
fmt::printf("{:5}", 42); // " 42"
- fmt::printf("{:05}", 42); // "00042"
+ fmt::printf("{:.5}", 42); // "00042"
A parametric format modifier is a secondary argument from the parameter list,
-which is a pointer to an instance of [[mods]]. This modifier parameter shall
-describe how the primary formattable argument is formatted.
+which is a pointer to an instance of [[mods]]. This modifier parameter
+describes how the primary formattable argument is formatted.
A parametric format sequence of this sort takes the form of "{i%j}", where i is
the formattable parameter index, j is the modifiers parameter index, and i & j
@@ -58,13 +62,13 @@ Some parametric modifier examples:
// "hello world hello"
fmt::printf("{%} {%} {0%1}", // evaluates to "{0%1} {2%3} {0%1}"
- "hello", &fmt::modifiers { ... },
- "world", &fmt::modifiers { ... });
+ "hello", &fmt::mods { ... },
+ "world", &fmt::mods { ... });
// "|hello| world|0000000123|BEEF|"
fmt::printf("|{%}|{%}|{%}|{%}|",
- "hello", &fmt::modifiers { ... },
- "world", &fmt::modifiers { width = 10, ... },
- 123, &fmt::modifiers { width = 10, padding = fmt::padding::ZEROES, ... },
- 0xBEEF, &fmt::modifiers { base = strconv::base::HEX, ... });
+ "hello", &fmt::mods { ... },
+ "world", &fmt::mods { width = 10, ... },
+ 123, &fmt::mods { prec = 10, ... },
+ 0xBEEF, &fmt::mods { base = strconv::base::HEX, ... });
diff --git a/fmt/fmt.ha b/fmt/fmt.ha
@@ -1,622 +0,0 @@
-// SPDX-License-Identifier: MPL-2.0
-// (c) Hare authors <https://harelang.org>
-
-use ascii;
-use encoding::utf8;
-use io;
-use math;
-use memio;
-use os;
-use strconv;
-use strings;
-use types;
-
-// Tagged union of the [[formattable]] types and [[mods]]. Used for
-// functions which accept format strings.
-export type field = (...formattable | *mods);
-
-// Tagged union of all types which are formattable.
-export type formattable = (...types::numeric | uintptr | str | rune | bool |
- nullable *opaque | void);
-
-// Formats text for printing and writes it to [[os::stdout]].
-export fn printf(fmt: str, args: field...) (size | io::error) =
- fprintf(os::stdout, fmt, args...);
-
-// Formats text for printing and writes it to [[os::stdout]], followed by a line
-// feed.
-export fn printfln(fmt: str, args: field...) (size | io::error) =
- fprintfln(os::stdout, fmt, args...);
-
-// Formats text for printing and writes it to [[os::stderr]].
-export fn errorf(fmt: str, args: field...) (size | io::error) =
- fprintf(os::stderr, fmt, args...);
-
-// Formats text for printing and writes it to [[os::stderr]], followed by a line
-// feed.
-export fn errorfln(fmt: str, args: field...) (size | io::error) =
- fprintfln(os::stderr, fmt, args...);
-
-// Formats text for printing and writes it into a heap-allocated string. The
-// caller must free the return value.
-export fn asprintf(fmt: str, args: field...) str = {
- let buf = memio::dynamic();
- assert(fprintf(&buf, fmt, args...) is size);
- return strings::fromutf8_unsafe(memio::buffer(&buf));
-};
-
-// Formats text for printing and writes it into a caller supplied buffer. The
-// returned string is borrowed from this buffer. Aborts if the buffer isn't
-// large enough to hold the formatted text.
-export fn bsprintf(buf: []u8, fmt: str, args: field...) str = {
- let sink = memio::fixed(buf);
- let l = fprintf(&sink, fmt, args...)!;
- return strings::fromutf8_unsafe(buf[..l]);
-};
-
-// Formats text for printing and writes it to [[os::stderr]], followed by a line
-// feed, then exits the program with an error status.
-export fn fatalf(fmt: str, args: field...) never = {
- fprintfln(os::stderr, fmt, args...)!;
- os::exit(255);
-};
-
-// Formats values for printing using the default format modifiers and writes
-// them to [[os::stderr]] separated by spaces and followed by a line feed, then
-// exits the program with an error status.
-export fn fatal(args: formattable...) never = {
- fprintln(os::stderr, args...)!;
- os::exit(255);
-};
-
-// Formats text for printing and writes it to an [[io::handle]], followed by a
-// line feed.
-export fn fprintfln(
- h: io::handle,
- fmt: str,
- args: field...
-) (size | io::error) = {
- return fprintf(h, fmt, args...)? + io::write(h, ['\n'])?;
-};
-
-// Formats values for printing using the default format modifiers and writes
-// them to [[os::stdout]] separated by spaces.
-export fn print(args: formattable...) (size | io::error) =
- fprint(os::stdout, args...);
-
-// Formats values for printing using the default format modifiers and writes
-// them to [[os::stdout]] separated by spaces and followed by a line feed.
-export fn println(args: formattable...) (size | io::error) =
- fprintln(os::stdout, args...);
-
-// Formats values for printing using the default format modifiers and writes
-// them to [[os::stderr]] separated by spaces.
-export fn error(args: formattable...) (size | io::error) =
- fprint(os::stderr, args...);
-
-// Formats values for printing using the default format modifiers and writes
-// them to [[os::stderr]] separated by spaces and followed by a line feed.
-export fn errorln(args: formattable...) (size | io::error) =
- fprintln(os::stderr, args...);
-
-// Formats values for printing using the default format modifiers and writes
-// them into a heap-allocated string separated by spaces. The caller must free
-// the return value.
-export fn asprint(args: formattable...) str = {
- let buf = memio::dynamic();
- assert(fprint(&buf, args...) is size);
- return strings::fromutf8_unsafe(memio::buffer(&buf));
-};
-
-// Formats values for printing using the default format modifiers and writes
-// them into a caller supplied buffer separated by spaces. The returned string
-// is borrowed from this buffer. Aborts if the buffer isn't large enough to hold
-// the formatted text.
-export fn bsprint(buf: []u8, args: formattable...) str = {
- let sink = memio::fixed(buf);
- let l = fprint(&sink, args...)!;
- return strings::fromutf8_unsafe(buf[..l]);
-};
-
-// Formats values for printing using the default format modifiers and writes
-// them to an [[io::handle]] separated by spaces and followed by a line feed.
-export fn fprintln(h: io::handle, args: formattable...) (size | io::error) = {
- return fprint(h, args...)? + io::write(h, ['\n'])?;
-};
-
-// Formats values for printing using the default format modifiers and writes
-// them to an [[io::handle]] separated by spaces.
-export fn fprint(h: io::handle, args: formattable...) (size | io::error) = {
- let mod = mods { base = strconv::base::DEC, ... };
- let n = 0z;
- for (let i = 0z; i < len(args); i += 1) {
- n += format(h, args[i], &mod)?;
- if (i != len(args) - 1) {
- n += io::write(h, [' '])?;
- };
- };
- return n;
-};
-
-// Specifies for numerical arguments when to prepend a plus or minus sign or a
-// blank space.
-export type negation = enum {
- NONE,
- SPACE,
- PLUS,
-};
-
-// Specifies how to align and pad an argument within a given width.
-export type padding = enum {
- ALIGN_RIGHT,
- ALIGN_LEFT,
- ZEROES,
-};
-
-// Specifies how to format an argument.
-export type mods = struct {
- padding: padding,
- negation: negation,
- width: uint,
- precision: uint,
- base: strconv::base,
-};
-
-type modflag = enum uint {
- NONE = 0,
- ZERO = 1 << 0,
- MINUS = 1 << 1,
- SPACE = 1 << 2,
- PLUS = 1 << 3,
-};
-
-type paramindex = (uint | nextparam | void);
-
-type nextparam = void;
-
-// Formats text for printing and writes it to an [[io::handle]].
-export fn fprintf(
- h: io::handle,
- fmt: str,
- args: field...
-) (size | io::error) = {
- let n = 0z, i = 0z;
- let checkunused = true;
- let iter = strings::iter(fmt);
- for (true) {
- let r: rune = match (strings::next(&iter)) {
- case void =>
- break;
- case let r: rune =>
- yield r;
- };
-
- if (r == '{') {
- r = match (strings::next(&iter)) {
- case void =>
- abort("Invalid format string (unterminated '{')");
- case let r: rune =>
- yield r;
- };
-
- if (r == '{') {
- n += io::write(h, utf8::encoderune('{'))?;
- continue;
- };
-
- const idx = if (ascii::isdigit(r)) {
- strings::prev(&iter);
- checkunused = false;
- yield scan_uint(&iter): size;
- } else {
- strings::prev(&iter);
- i += 1;
- yield i - 1;
- };
- assert(idx < len(args), "Not enough parameters given");
-
- const arg = match (args[idx]) {
- case let arg: formattable =>
- yield arg;
- case =>
- abort("Invalid formattable");
- };
-
- r = match (strings::next(&iter)) {
- case void =>
- abort("Invalid format string (unterminated '{')");
- case let r: rune =>
- yield r;
- };
-
- let mod = &mods { ... };
- let pi: paramindex = void;
- switch (r) {
- case ':' =>
- scan_inline_modifiers(&iter, mod);
- case '%' =>
- scan_parametric_modifiers(&iter, &pi);
- case '}' => void;
- case =>
- abort("Invalid format string");
- };
-
- match (pi) {
- case let pi: uint =>
- checkunused = false;
- match (args[pi]) {
- case let pmod: *mods =>
- mod = pmod;
- case =>
- abort("Explicit parameter is not *fmt::mods");
- };
- case nextparam =>
- i += 1;
- match (args[i - 1]) {
- case let pmod: *mods =>
- mod = pmod;
- case =>
- abort("Implicit parameter is not *fmt::mods");
- };
- case void => void;
- };
-
- n += format(h, arg, mod)?;
- } else if (r == '}') {
- match (strings::next(&iter)) {
- case void =>
- abort("Invalid format string (hanging '}')");
- case let r: rune =>
- assert(r == '}', "Invalid format string (hanging '}')");
- };
-
- n += io::write(h, utf8::encoderune('}'))?;
- } else {
- n += io::write(h, utf8::encoderune(r))?;
- };
- };
-
- assert(!checkunused || i == len(args), "Too many parameters given");
- return n;
-};
-
-fn format(
- out: io::handle,
- arg: formattable,
- mod: *mods,
-) (size | io::error) = {
- 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 (reprlen < mod.width: size) {
- padlen = mod.width: size - reprlen;
- pad = utf8::encoderune(switch (mod.padding) {
- case padding::ZEROES =>
- yield '0';
- case =>
- yield ' ';
- });
- };
-
- 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;
-};
-
-fn format_raw(
- out: io::handle,
- arg: formattable,
- mod: *mods,
- padlen: size,
- pad: []u8,
-) (size | io::error) = {
- match (arg) {
- case let s: str =>
- return io::write(out, strings::toutf8(s));
- case let r: rune =>
- return io::write(out, utf8::encoderune(r));
- case let b: bool =>
- return io::write(out,
- strings::toutf8(if (b) "true" else "false"));
- case let n: types::numeric =>
- 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 *opaque =>
- match (v) {
- case let v: *opaque =>
- let z = io::write(out, strings::toutf8("0x"))?;
- const s = strconv::uptrtosb(v: uintptr,
- strconv::base::HEX_LOWER);
- z += io::write(out, strings::toutf8(s))?;
- return z;
- case null =>
- 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: *mods) (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 copy = *iter;
- for (true) {
- let r = match (strings::next(iter)) {
- case void =>
- abort("Invalid format string (unterminated '{')");
- case let r: rune =>
- yield r;
- };
-
- if (!ascii::isdigit(r)) {
- strings::prev(iter);
- match (strconv::stou(strings::slice(©, iter))) {
- case (strconv::invalid | strconv::overflow) =>
- abort("Invalid format string (invalid index)");
- case let u: uint =>
- return u;
- };
- };
- };
- abort("unreachable");
-};
-
-fn scan_modifier_flags(iter: *strings::iterator, mod: *mods) void = {
- let flags = modflag::NONE;
-
- for (true) {
- let r = match (strings::next(iter)) {
- case void =>
- abort("Invalid format string (unterminated '{')");
- case let r: rune =>
- yield r;
- };
-
- switch (r) {
- case '0' =>
- flags |= modflag::ZERO;
- case '-' =>
- flags |= modflag::MINUS;
- case ' ' =>
- flags |= modflag::SPACE;
- case '+' =>
- flags |= modflag::PLUS;
- case =>
- strings::prev(iter);
- break;
- };
- };
-
- mod.padding = if (flags & modflag::MINUS != 0)
- padding::ALIGN_LEFT
- else if (flags & modflag::ZERO != 0)
- padding::ZEROES
- else
- padding::ALIGN_RIGHT;
-
- mod.negation = if (flags & modflag::PLUS != 0)
- negation::PLUS
- else if (flags & modflag::SPACE != 0)
- negation::SPACE
- else
- negation::NONE;
-};
-
-fn scan_modifier_width(iter: *strings::iterator, mod: *mods) void = {
- let r = match (strings::next(iter)) {
- case void =>
- abort("Invalid format string (unterminated '{')");
- case let r: rune =>
- yield r;
- };
-
- let is_digit = ascii::isdigit(r);
- strings::prev(iter);
-
- if (is_digit) {
- mod.width = scan_uint(iter);
- };
-};
-
-fn scan_modifier_precision(iter: *strings::iterator, mod: *mods) void = {
- let r = match (strings::next(iter)) {
- case void =>
- abort("Invalid format string (unterminated '{')");
- case let r: rune =>
- yield r;
- };
-
- if (r == '.') {
- mod.precision = scan_uint(iter);
- } else {
- strings::prev(iter);
- };
-};
-
-fn scan_modifier_base(iter: *strings::iterator, mod: *mods) void = {
- let r = match (strings::next(iter)) {
- case void =>
- abort("Invalid format string (unterminated '{')");
- case let r: rune =>
- yield r;
- };
-
- switch (r) {
- case 'x' =>
- mod.base = strconv::base::HEX_LOWER;
- case 'X' =>
- mod.base = strconv::base::HEX_UPPER;
- case 'o' =>
- mod.base = strconv::base::OCT;
- case 'b' =>
- mod.base = strconv::base::BIN;
- case =>
- strings::prev(iter);
- };
-};
-
-fn scan_inline_modifiers(iter: *strings::iterator, mod: *mods) void = {
- scan_modifier_flags(iter, mod);
- scan_modifier_width(iter, mod);
- scan_modifier_precision(iter, mod);
- scan_modifier_base(iter, mod);
-
- // eat '}'
- let terminated = match (strings::next(iter)) {
- case void =>
- yield false;
- case let r: rune =>
- yield r == '}';
- };
- assert(terminated, "Invalid format string (unterminated '{')");
-};
-
-fn scan_parameter_index(iter: *strings::iterator, pi: *paramindex) void = {
- let r = match (strings::next(iter)) {
- case void =>
- abort("Invalid format string (unterminated '{')");
- case let r: rune =>
- yield r;
- };
-
- let is_digit = ascii::isdigit(r);
- strings::prev(iter);
- if (is_digit) {
- *pi = scan_uint(iter);
- } else {
- *pi = nextparam;
- };
-};
-
-fn scan_parametric_modifiers(iter: *strings::iterator, pi: *paramindex) void = {
- scan_parameter_index(iter, pi);
-
- // eat '}'
- let terminated = match (strings::next(iter)) {
- case void =>
- yield false;
- case let r: rune =>
- yield r == '}';
- };
- assert(terminated, "Invalid format string (unterminated '{')");
-};
-
-@test fn fmt() void = {
- let buf: [1024]u8 = [0...];
-
- assert(bsprint(buf, "hello world") == "hello world");
- assert(bsprintf(buf, "hello world") == "hello world");
- assert(bsprintf(buf, "{} {}", "hello", "world") == "hello world");
- assert(bsprintf(buf, "{0} {1}", "hello", "world") == "hello world");
- assert(bsprintf(buf, "{0} {0}", "hello", "world") == "hello hello");
- assert(bsprintf(buf, "{1} {0} {1}", "hello", "world") == "world hello world");
-
- const mod = &mods { width = 7, ... };
- assert(bsprintf(buf, "{%}", "hello", mod) == " hello");
- assert(bsprintf(buf, "{%1}", "hello", mod) == " hello");
- assert(bsprintf(buf, "{0%1}", "hello", mod) == " hello");
- assert(bsprintf(buf, "{0%2}", "hello", 0, mod) == " hello");
- assert(bsprintf(buf, "{1%2}", 0, "hello", mod) == " hello");
- assert(bsprintf(buf, "{2%0}", mod, 0, "hello") == " hello");
- assert(bsprintf(buf, "{2%}", mod, 0, "hello") == " hello");
- assert(bsprintf(buf, "|{1%}|{}|", mod, "hello") == "| hello|hello|");
- assert(bsprintf(buf, "|{}|{2%}|", "hello", mod, "world") == "|hello| world|");
- assert(bsprintf(buf, "|{%}|{%}|{%}|{%}|",
- "hello", &mods { ... },
- "world", &mods { width = 10, ... },
- 123, &mods { width = 10, padding = padding::ZEROES, ... },
- 0xBEEF, &mods { base = strconv::base::HEX, ... },
- ) == "|hello| world|0000000123|BEEF|");
- assert(bsprintf(buf, "|{%}|{%}|{0%1}|",
- "hello", &mods { ... },
- "world", &mods { ... },
- ) == "|hello|world|hello|");
-
- 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/fmt/iter.ha b/fmt/iter.ha
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use ascii;
+use strings;
+use strconv;
+use types;
+
+// Tagged union of the [[formattable]] types and [[mods]]. Used for
+// functions which accept format strings.
+export type field = (...formattable | *mods);
+
+// Tagged union of all types which are formattable.
+export type formattable = (...types::numeric | uintptr | str | rune | bool |
+ nullable *opaque | void);
+
+// Specifies for numerical arguments when to prepend a plus or minus sign or a
+// blank space.
+export type neg = enum {
+ NONE,
+ SPACE,
+ PLUS,
+};
+
+// Specifies how to align an argument within a given width.
+export type alignment = enum {
+ RIGHT,
+ LEFT,
+};
+
+// Specifies how to format an argument.
+export type mods = struct {
+ alignment: alignment,
+ pad: rune,
+ neg: neg,
+ width: size,
+ prec: size,
+ base: strconv::base,
+};
+
+type iterator = struct {
+ iter: strings::iterator,
+ args: []field,
+ idx: size,
+ checkunused: bool,
+};
+
+fn iter(fmt: str, args: []field) iterator = iterator {
+ iter = strings::iter(fmt),
+ args = args,
+ idx = 0,
+ checkunused = true,
+};
+
+fn next(it: *iterator) (str | (formattable, mods) | void) = {
+ let r = match (strings::next(&it.iter)) {
+ case void =>
+ return void;
+ case let r: rune =>
+ yield r;
+ };
+ switch (r) {
+ case '{' => void; // handled below
+ case '}' =>
+ match (strings::next(&it.iter)) {
+ case void =>
+ abort("Invalid format string (hanging '}')");
+ case let r: rune =>
+ assert(r == '}', "Invalid format string (hanging '}')");
+ };
+ return "}";
+ case =>
+ strings::prev(&it.iter);
+ let start = it.iter;
+ for (true) match (strings::next(&it.iter)) {
+ case void => break;
+ case let r: rune =>
+ if (r == '{' || r == '}') {
+ strings::prev(&it.iter);
+ break;
+ };
+ };
+ return strings::slice(&start, &it.iter);
+ };
+
+ r = getrune(it);
+ if (r == '{') {
+ return "{";
+ };
+
+ let idx = if (ascii::isdigit(r)) {
+ strings::prev(&it.iter);
+ it.checkunused = false;
+ defer r = getrune(it);
+ yield scan_sz(it);
+ } else {
+ defer it.idx += 1;
+ yield it.idx;
+ };
+ assert(idx < len(it.args), "Not enough parameters given");
+ let arg = it.args[idx] as formattable;
+ let mod = mods { ... };
+
+ switch (r) {
+ case ':' =>
+ scan_modifiers(it, &mod);
+ case '%' =>
+ r = getrune(it);
+ let idx = if (ascii::isdigit(r)) {
+ strings::prev(&it.iter);
+ it.checkunused = false;
+ defer r = getrune(it);
+ yield scan_sz(it);
+ } else {
+ defer it.idx += 1;
+ yield it.idx;
+ };
+ assert(idx < len(it.args), "Not enough parameters given");
+ mod = *(it.args[idx] as *mods);
+ assert(r == '}', "Invalid format string (didn't find '}' after modifier index)");
+ case '}' => void;
+ case => abort("Invalid format string");
+ };
+
+ return (arg, mod);
+};
+
+fn scan_modifiers(it: *iterator, mod: *mods) void = {
+ mod.pad = ' ';
+ for (true) switch (getrune(it)) {
+ // alignment
+ case '-' => mod.alignment = alignment::LEFT;
+ // padding
+ case '_' => mod.pad = getrune(it);
+ // negation
+ case ' ' => mod.neg = neg::SPACE;
+ case '+' => mod.neg = neg::PLUS;
+ // base
+ case 'x' => mod.base = strconv::base::HEX_LOWER;
+ case 'X' => mod.base = strconv::base::HEX_UPPER;
+ case 'o' => mod.base = strconv::base::OCT;
+ case 'b' => mod.base = strconv::base::BIN;
+ // precision
+ case '.' => mod.prec = scan_sz(it);
+ // width
+ case '1', '2', '3', '4', '5', '6', '7', '8', '9' =>
+ strings::prev(it);
+ mod.width = scan_sz(it);
+ case =>
+ strings::prev(it);
+ break;
+ };
+ assert(getrune(it) == '}', "Invalid format string (unterminated '{')");
+};
+
+fn scan_sz(it: *iterator) size = {
+ let start = it.iter;
+ assert(ascii::isdigit(getrune(it)));
+ for (ascii::isdigit(getrune(it))) void;
+ strings::prev(&it.iter);
+
+ match (strconv::stoz(strings::slice(&start, &it.iter))) {
+ case strconv::invalid =>
+ abort("Invalid format string (invalid integer)");
+ case strconv::overflow =>
+ abort("Invalid format string (integer overflow)");
+ case let z: size =>
+ return z;
+ };
+};
+
+fn getrune(it: *iterator) rune = match (strings::next(&it.iter)) {
+case void =>
+ abort("Invalid format string (unterminated '{')");
+case let r: rune =>
+ return r;
+};
diff --git a/fmt/print.ha b/fmt/print.ha
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use encoding::utf8;
+use io;
+use math;
+use strconv;
+use strings;
+use types;
+
+// Formats values for printing using the default format modifiers and writes
+// them to an [[io::handle]] separated by spaces.
+export fn fprint(h: io::handle, args: formattable...) (io::error | size) = {
+ let mod = mods { ... };
+ let n = 0z;
+ for (let i = 0z; i < len(args); i += 1) {
+ n += format(h, args[i], &mod)?;
+ if (i != len(args) - 1) {
+ n += format(h, " ", &mod)?;
+ };
+ };
+ return n;
+};
+
+// Formats text for printing and writes it to an [[io::handle]].
+export fn fprintf(
+ h: io::handle,
+ fmt: str,
+ args: field...
+) (io::error | size) = {
+ let n = 0z, i = 0z;
+ let it = iter(fmt, args);
+ for (true) match (next(&it)) {
+ case void => break;
+ case let s: str =>
+ n += format(h, s, &mods { ... })?;
+ case let f: (formattable, mods) =>
+ n += format(h, f.0, &f.1)?;
+ };
+
+ assert(!it.checkunused || it.idx == len(args), "Too many parameters given");
+ return n;
+};
+
+fn format(
+ out: io::handle,
+ arg: formattable,
+ mod: *mods,
+) (size | io::error) = {
+ let start = 0z;
+ // guaranteed not to have starting padding in either of these cases
+ // saves the extra format_raw()
+ if (mod.width > 0 && mod.alignment != alignment::LEFT) {
+ let width = format_raw(io::empty, arg, mod)?;
+ let pad = if (width > mod.width) 0z else mod.width - width;
+
+ switch (mod.alignment) {
+ case alignment::LEFT => abort();
+ case alignment::RIGHT => start = pad;
+ };
+ };
+
+ let z = 0z;
+ for (z < start) {
+ z += io::write(out, utf8::encoderune(mod.pad))?;
+ };
+ z += format_raw(out, arg, mod)?;
+ for (z < mod.width) {
+ z += io::write(out, utf8::encoderune(mod.pad))?;
+ };
+
+ return z;
+};
+
+fn format_raw(
+ out: io::handle,
+ arg: formattable,
+ mod: *mods,
+) (size | io::error) = match (arg) {
+case void =>
+ return io::write(out, strings::toutf8("void"));
+case let r: rune =>
+ return io::write(out, utf8::encoderune(r));
+case let s: str =>
+ if (mod.prec != 0) s = strings::sub(s, 0, mod.prec);
+ return io::write(out, strings::toutf8(s));
+case let b: bool =>
+ return io::write(out, strings::toutf8(if (b) "true" else "false"));
+case let p: uintptr =>
+ const s = strconv::uptrtosb(p, mod.base);
+ return io::write(out, strings::toutf8(s));
+case let v: nullable *opaque =>
+ match (v) {
+ case let v: *opaque =>
+ let z = io::write(out, strings::toutf8("0x"))?;
+ const s = strconv::uptrtosb(v: uintptr,
+ strconv::base::HEX_LOWER);
+ z += io::write(out, strings::toutf8(s))?;
+ return z;
+ case null =>
+ return io::write(out, strings::toutf8("(null)"));
+ };
+case let f: types::floating =>
+ assert(mod.base == strconv::base::DEFAULT
+ || mod.base == strconv::base::DEC); // TODO
+ assert(mod.prec <= types::UINT_MAX);
+ // TODO: mod.prec should be (size | void) but that needs @default
+ return strconv::fftosf(out, f, strconv::ffmt::G,
+ if (mod.prec != 0) mod.prec: uint else void, 0)?;
+case let i: types::integer =>
+ let neg = match (i) {
+ case types::unsigned =>
+ yield false;
+ case let s: types::signed =>
+ i = math::absi(s);
+ yield math::signi(s) < 0;
+ };
+ let sign = if (neg) "-" else {
+ yield switch (mod.neg) {
+ case neg::PLUS => yield "+";
+ case neg::SPACE => yield " ";
+ case neg::NONE => yield "";
+ };
+ };
+
+ let i = strconv::integertosb(i, mod.base);
+ let pad = if (mod.prec < len(sign) + len(i)) {
+ yield 0z;
+ } else {
+ yield mod.prec - len(sign) - len(i);
+ };
+
+ let z = io::write(out, strings::toutf8(sign))?;
+ for (let i = 0z; i < pad) {
+ i += io::write(out, ['0'])?;
+ };
+ z += pad;
+ return z + io::write(out, strings::toutf8(i))?;
+};
diff --git a/fmt/wrappers.ha b/fmt/wrappers.ha
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use io;
+use memio;
+use os;
+use strings;
+
+// Formats text for printing and writes it to [[os::stdout]].
+export fn printf(fmt: str, args: field...) (io::error | size) =
+ fprintf(os::stdout, fmt, args...);
+
+// Formats text for printing and writes it to [[os::stdout]], followed by a line
+// feed.
+export fn printfln(fmt: str, args: field...) (io::error | size) =
+ fprintfln(os::stdout, fmt, args...);
+
+// Formats text for printing and writes it to [[os::stderr]].
+export fn errorf(fmt: str, args: field...) (io::error | size) =
+ fprintf(os::stderr, fmt, args...);
+
+// Formats text for printing and writes it to [[os::stderr]], followed by a line
+// feed.
+export fn errorfln(fmt: str, args: field...) (io::error | size) =
+ fprintfln(os::stderr, fmt, args...);
+
+// Formats text for printing and writes it into a heap-allocated string. The
+// caller must free the return value.
+export fn asprintf(fmt: str, args: field...) str = {
+ let buf = memio::dynamic();
+ assert(fprintf(&buf, fmt, args...) is size);
+ return strings::fromutf8_unsafe(memio::buffer(&buf));
+};
+
+// Formats text for printing and writes it into a caller supplied buffer. The
+// returned string is borrowed from this buffer. Aborts if the buffer isn't
+// large enough to hold the formatted text.
+export fn bsprintf(buf: []u8, fmt: str, args: field...) str = {
+ let sink = memio::fixed(buf);
+ let l = fprintf(&sink, fmt, args...)!;
+ return strings::fromutf8_unsafe(buf[..l]);
+};
+
+// Formats text for printing and writes it to [[os::stderr]], followed by a line
+// feed, then exits the program with an error status.
+export fn fatalf(fmt: str, args: field...) never = {
+ fprintfln(os::stderr, fmt, args...)!;
+ os::exit(255);
+};
+
+// Formats values for printing using the default format modifiers and writes
+// them to [[os::stderr]] separated by spaces and followed by a line feed, then
+// exits the program with an error status.
+export fn fatal(args: formattable...) never = {
+ fprintln(os::stderr, args...)!;
+ os::exit(255);
+};
+
+// Formats text for printing and writes it to an [[io::handle]], followed by a
+// line feed.
+export fn fprintfln(
+ h: io::handle,
+ fmt: str,
+ args: field...
+) (io::error | size) = fprintf(h, fmt, args...)? + io::write(h, ['\n'])?;
+
+// Formats values for printing using the default format modifiers and writes
+// them to [[os::stdout]] separated by spaces.
+export fn print(args: formattable...) (io::error | size) =
+ fprint(os::stdout, args...);
+
+// Formats values for printing using the default format modifiers and writes
+// them to [[os::stdout]] separated by spaces and followed by a line feed.
+export fn println(args: formattable...) (io::error | size) =
+ fprintln(os::stdout, args...);
+
+// Formats values for printing using the default format modifiers and writes
+// them to [[os::stderr]] separated by spaces.
+export fn error(args: formattable...) (io::error | size) =
+ fprint(os::stderr, args...);
+
+// Formats values for printing using the default format modifiers and writes
+// them to [[os::stderr]] separated by spaces and followed by a line feed.
+export fn errorln(args: formattable...) (io::error | size) =
+ fprintln(os::stderr, args...);
+
+// Formats values for printing using the default format modifiers and writes
+// them into a heap-allocated string separated by spaces. The caller must free
+// the return value.
+export fn asprint(args: formattable...) str = {
+ let buf = memio::dynamic();
+ assert(fprint(&buf, args...) is size);
+ return strings::fromutf8_unsafe(memio::buffer(&buf));
+};
+
+// Formats values for printing using the default format modifiers and writes
+// them into a caller supplied buffer separated by spaces. The returned string
+// is borrowed from this buffer. Aborts if the buffer isn't large enough to hold
+// the formatted text.
+export fn bsprint(buf: []u8, args: formattable...) str = {
+ let sink = memio::fixed(buf);
+ let l = fprint(&sink, args...)!;
+ return strings::fromutf8_unsafe(buf[..l]);
+};
+
+// Formats values for printing using the default format modifiers and writes
+// them to an [[io::handle]] separated by spaces and followed by a line feed.
+export fn fprintln(h: io::handle, args: formattable...) (io::error | size) =
+ fprint(h, args...)? + io::write(h, ['\n'])?;
diff --git a/makefiles/freebsd.aarch64.mk b/makefiles/freebsd.aarch64.mk
@@ -141,7 +141,7 @@ $(HARECACHE)/strconv.ssa: $(strconv_ha) $(HARECACHE)/ascii.td $(HARECACHE)/bytes
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/strconv.ssa -t $(HARECACHE)/strconv.td.tmp -N strconv $(strconv_ha)
-fmt_ha = fmt/fmt.ha
+fmt_ha = fmt/iter.ha fmt/print.ha fmt/wrappers.ha
$(HARECACHE)/fmt.ssa: $(fmt_ha) $(HARECACHE)/ascii.td $(HARECACHE)/encoding_utf8.td $(HARECACHE)/io.td $(HARECACHE)/math.td $(HARECACHE)/memio.td $(HARECACHE)/os.td $(HARECACHE)/strconv.td $(HARECACHE)/strings.td $(HARECACHE)/types.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"
diff --git a/makefiles/freebsd.riscv64.mk b/makefiles/freebsd.riscv64.mk
@@ -141,7 +141,7 @@ $(HARECACHE)/strconv.ssa: $(strconv_ha) $(HARECACHE)/ascii.td $(HARECACHE)/bytes
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/strconv.ssa -t $(HARECACHE)/strconv.td.tmp -N strconv $(strconv_ha)
-fmt_ha = fmt/fmt.ha
+fmt_ha = fmt/iter.ha fmt/print.ha fmt/wrappers.ha
$(HARECACHE)/fmt.ssa: $(fmt_ha) $(HARECACHE)/ascii.td $(HARECACHE)/encoding_utf8.td $(HARECACHE)/io.td $(HARECACHE)/math.td $(HARECACHE)/memio.td $(HARECACHE)/os.td $(HARECACHE)/strconv.td $(HARECACHE)/strings.td $(HARECACHE)/types.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"
diff --git a/makefiles/freebsd.x86_64.mk b/makefiles/freebsd.x86_64.mk
@@ -141,7 +141,7 @@ $(HARECACHE)/strconv.ssa: $(strconv_ha) $(HARECACHE)/ascii.td $(HARECACHE)/bytes
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/strconv.ssa -t $(HARECACHE)/strconv.td.tmp -N strconv $(strconv_ha)
-fmt_ha = fmt/fmt.ha
+fmt_ha = fmt/iter.ha fmt/print.ha fmt/wrappers.ha
$(HARECACHE)/fmt.ssa: $(fmt_ha) $(HARECACHE)/ascii.td $(HARECACHE)/encoding_utf8.td $(HARECACHE)/io.td $(HARECACHE)/math.td $(HARECACHE)/memio.td $(HARECACHE)/os.td $(HARECACHE)/strconv.td $(HARECACHE)/strings.td $(HARECACHE)/types.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"
diff --git a/makefiles/linux.aarch64.mk b/makefiles/linux.aarch64.mk
@@ -159,7 +159,7 @@ $(HARECACHE)/strconv.ssa: $(strconv_ha) $(HARECACHE)/ascii.td $(HARECACHE)/bytes
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/strconv.ssa -t $(HARECACHE)/strconv.td.tmp -N strconv $(strconv_ha)
-fmt_ha = fmt/fmt.ha
+fmt_ha = fmt/iter.ha fmt/print.ha fmt/wrappers.ha
$(HARECACHE)/fmt.ssa: $(fmt_ha) $(HARECACHE)/ascii.td $(HARECACHE)/encoding_utf8.td $(HARECACHE)/io.td $(HARECACHE)/math.td $(HARECACHE)/memio.td $(HARECACHE)/os.td $(HARECACHE)/strconv.td $(HARECACHE)/strings.td $(HARECACHE)/types.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"
diff --git a/makefiles/linux.riscv64.mk b/makefiles/linux.riscv64.mk
@@ -159,7 +159,7 @@ $(HARECACHE)/strconv.ssa: $(strconv_ha) $(HARECACHE)/ascii.td $(HARECACHE)/bytes
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/strconv.ssa -t $(HARECACHE)/strconv.td.tmp -N strconv $(strconv_ha)
-fmt_ha = fmt/fmt.ha
+fmt_ha = fmt/iter.ha fmt/print.ha fmt/wrappers.ha
$(HARECACHE)/fmt.ssa: $(fmt_ha) $(HARECACHE)/ascii.td $(HARECACHE)/encoding_utf8.td $(HARECACHE)/io.td $(HARECACHE)/math.td $(HARECACHE)/memio.td $(HARECACHE)/os.td $(HARECACHE)/strconv.td $(HARECACHE)/strings.td $(HARECACHE)/types.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"
diff --git a/makefiles/linux.x86_64.mk b/makefiles/linux.x86_64.mk
@@ -159,7 +159,7 @@ $(HARECACHE)/strconv.ssa: $(strconv_ha) $(HARECACHE)/ascii.td $(HARECACHE)/bytes
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/strconv.ssa -t $(HARECACHE)/strconv.td.tmp -N strconv $(strconv_ha)
-fmt_ha = fmt/fmt.ha
+fmt_ha = fmt/iter.ha fmt/print.ha fmt/wrappers.ha
$(HARECACHE)/fmt.ssa: $(fmt_ha) $(HARECACHE)/ascii.td $(HARECACHE)/encoding_utf8.td $(HARECACHE)/io.td $(HARECACHE)/math.td $(HARECACHE)/memio.td $(HARECACHE)/os.td $(HARECACHE)/strconv.td $(HARECACHE)/strings.td $(HARECACHE)/types.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"
diff --git a/test/+test.ha b/test/+test.ha
@@ -179,7 +179,7 @@ export @symbol("__test_main") fn main() size = {
if (len(ctx.skipped) > 0) {
fmt::print(len(ctx.skipped), "skipped; ")!;
};
- fmt::printfln("{} completed in {}.{:09}s", total_cnt,
+ fmt::printfln("{} completed in {}.{:.9}s", total_cnt,
elapsed_whole, elapsed_fraction)!;
easter_egg(ctx.failures, enabled_tests);
@@ -209,7 +209,7 @@ fn do_test(ctx: *context, test: test) void = {
const time_diff = time::diff(start_time, end_time);
assert(time_diff >= 0);
ctx.total_time += time_diff;
- fmt::printfln(" in {}.{:09}s",
+ fmt::printfln(" in {}.{:.9}s",
time_diff / 1000000000,
time_diff % 1000000000)!;
diff --git a/time/date/format.ha b/time/date/format.ha
@@ -108,45 +108,45 @@ fn fmtout(out: io::handle, r: rune, d: *date) (size | io::error) = {
case 'B' =>
return fmt::fprint(out, MONTHS[_month(d) - 1]);
case 'd' =>
- return fmt::fprintf(out, "{:02}", _day(d));
+ return fmt::fprintf(out, "{:.2}", _day(d));
case 'e' =>
return fmt::fprintf(out, "{: 2}", _day(d));
case 'F' =>
- return fmt::fprintf(out, "{:04}-{:02}-{:02}", _year(d), _month(d), _day(d));
+ return fmt::fprintf(out, "{:.4}-{:.2}-{:.2}", _year(d), _month(d), _day(d));
case 'H' =>
- return fmt::fprintf(out, "{:02}", _hour(d));
+ return fmt::fprintf(out, "{:.2}", _hour(d));
case 'I' =>
- return fmt::fprintf(out, "{:02}", (_hour(d) + 11) % 12 + 1);
+ return fmt::fprintf(out, "{:.2}", (_hour(d) + 11) % 12 + 1);
case 'j' =>
- return fmt::fprintf(out, "{:03}", _yearday(d));
+ return fmt::fprintf(out, "{:.3}", _yearday(d));
case 'L' =>
return fmt::fprint(out, d.loc.name);
case 'm' =>
- return fmt::fprintf(out, "{:02}", _month(d));
+ return fmt::fprintf(out, "{:.2}", _month(d));
case 'M' =>
- return fmt::fprintf(out, "{:02}", _minute(d));
+ return fmt::fprintf(out, "{:.2}", _minute(d));
case 'N' =>
- return fmt::fprintf(out, "{:09}", _nanosecond(d));
+ return fmt::fprintf(out, "{:.9}", _nanosecond(d));
case 'p' =>
return fmt::fprint(out, if (_hour(d) < 12) "AM" else "PM");
case 's' =>
- return fmt::fprintf(out, "{:02}", time::unix(*(d: *time::instant)));
+ return fmt::fprintf(out, "{:.2}", time::unix(*(d: *time::instant)));
case 'S' =>
- return fmt::fprintf(out, "{:02}", _second(d));
+ return fmt::fprintf(out, "{:.2}", _second(d));
case 'T' =>
- return fmt::fprintf(out, "{:02}:{:02}:{:02}", _hour(d), _minute(d), _second(d));
+ return fmt::fprintf(out, "{:.2}:{:.2}:{:.2}", _hour(d), _minute(d), _second(d));
case 'u' =>
return fmt::fprintf(out, "{}", _weekday(d) + 1);
case 'U' =>
- return fmt::fprintf(out, "{:02}", _sundayweek(d));
+ return fmt::fprintf(out, "{:.2}", _sundayweek(d));
case 'w' =>
return fmt::fprintf(out, "{}", (_weekday(d) + 1) % 7);
case 'W' =>
- return fmt::fprintf(out, "{:02}", _week(d));
+ return fmt::fprintf(out, "{:.2}", _week(d));
case 'y' =>
- return fmt::fprintf(out, "{:02}", _year(d) % 100);
+ return fmt::fprintf(out, "{:.2}", _year(d) % 100);
case 'Y' =>
- return fmt::fprintf(out, "{:04}", _year(d));
+ return fmt::fprintf(out, "{:.4}", _year(d));
case 'z' =>
const (sign, zo) = if (chrono::mzone(d).zoff >= 0) {
yield ('+', calc_hmsn(chrono::mzone(d).zoff));
@@ -154,7 +154,7 @@ fn fmtout(out: io::handle, r: rune, d: *date) (size | io::error) = {
yield ('-', calc_hmsn(-chrono::mzone(d).zoff));
};
const (hr, mi) = (zo.0, zo.1);
- return fmt::fprintf(out, "{}{:02}{:02}", sign, hr, mi);
+ return fmt::fprintf(out, "{}{:.2}{:.2}", sign, hr, mi);
case 'Z' =>
return fmt::fprint(out, chrono::mzone(d).abbr);
case '%' =>
diff --git a/uuid/uuid.ha b/uuid/uuid.ha
@@ -56,20 +56,20 @@ export fn compare(a: uuid, b: uuid) bool = bytes::equal(a, b);
export fn encode(out: io::handle, in: uuid) (size | io::error) = {
let z = 0z;
for (let i = TIME_LOW; i < TIME_LOW + 4; i += 1) {
- z += fmt::fprintf(out, "{:02x}", in[i])?;
+ z += fmt::fprintf(out, "{:.2x}", in[i])?;
};
z += fmt::fprintf(out, "-")?;
for (let i = TIME_MID; i < TIME_MID + 2; i += 1) {
- z += fmt::fprintf(out, "{:02x}", in[i])?;
+ z += fmt::fprintf(out, "{:.2x}", in[i])?;
};
z += fmt::fprintf(out, "-")?;
for (let i = TIME_HI_AND_VERSION; i < TIME_HI_AND_VERSION + 2; i += 1) {
- z += fmt::fprintf(out, "{:02x}", in[i])?;
+ z += fmt::fprintf(out, "{:.2x}", in[i])?;
};
- z += fmt::fprintf(out, "-{:02x}{:02x}-",
+ z += fmt::fprintf(out, "-{:.2x}{:.2x}-",
in[CLOCK_SEQ_HI_AND_RESERVED], in[CLOCK_SEQ_LOW])?;
for (let i = NODE; i < NODE + 6; i += 1) {
- z += fmt::fprintf(out, "{:02x}", in[i])?;
+ z += fmt::fprintf(out, "{:.2x}", in[i])?;
};
return z;
};