hare

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

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:
Mcrypto/bcrypt/bcrypt.ha | 2+-
Mcrypto/blake2b/+test.ha | 2+-
Mcrypto/curve25519/+test.ha | 4++--
Mencoding/hex/hex.ha | 4++--
Afmt/+test.ha | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mfmt/README | 68++++++++++++++++++++++++++++++++++++--------------------------------
Dfmt/fmt.ha | 622-------------------------------------------------------------------------------
Afmt/iter.ha | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afmt/print.ha | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afmt/wrappers.ha | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmakefiles/freebsd.aarch64.mk | 2+-
Mmakefiles/freebsd.riscv64.mk | 2+-
Mmakefiles/freebsd.x86_64.mk | 2+-
Mmakefiles/linux.aarch64.mk | 2+-
Mmakefiles/linux.riscv64.mk | 2+-
Mmakefiles/linux.x86_64.mk | 2+-
Mtest/+test.ha | 4++--
Mtime/date/format.ha | 32++++++++++++++++----------------
Muuid/uuid.ha | 10+++++-----
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(&copy, 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; };