hare

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

commit 7f0e8f75fe7d437561b8a705aa060ea5e053bbf3
parent 2264611b30f7fe9db43a72ced79e5c66204a0d49
Author: Drew DeVault <sir@cmpwn.com>
Date:   Fri,  5 Feb 2021 16:08:12 -0500

fmt: flesh out a bit more

Diffstat:
Mfmt/fmt.ha | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 111 insertions(+), 24 deletions(-)

diff --git a/fmt/fmt.ha b/fmt/fmt.ha @@ -4,13 +4,38 @@ // // 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 selected by indexing it from zero: '{0}', '{1}', and so on. +// be selected by indexing it from zero: '{0}', '{1}', and so on. To print '{', +// use '{{', and for '}', use '}}'. // // You may use a colon to add format modifiers; for example, '{:x}' will format -// an argument in hexadecimal, and '{3:10}' will pad the 3rd argument to 10 -// characters. +// an argument in hexadecimal, and '{3:-10}' will left-align the 3rd argument to +// at least 10 characters. // -// TODO: Expand on format modifiers. +// The format modifiers takes the form of an optional flag character: +// +// 0: Numeric values are zero-padded up to the required width. +// -: The value shall be left-aligned, and spaces inserted on the right to meet +// the required width. '-' takes precedence over '0' if both are used. +// : (a space) insert a space before positive numbers, where '-' would be if it +// were negative. +// +: insert a '+' before positive numbers, where '-' would be if it were +// negative. '+' takes precedence over ' ' if both are used. +// +// Following the flag, an optional decimal number shall specify the minimum +// width 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 +// +// TODO: Expand this with more format modifiers use ascii; use encoding::utf8; use io; @@ -31,6 +56,34 @@ export fn printf(fmt: str, args: formattable...) (io::error | size) = export fn errorf(fmt: str, args: formattable...) (io::error | size) = fprintf(os::stderr, fmt, args...); +type base = enum { + DECIMAL, + LOWER_HEX, + UPPER_HEX, + OCTAL, + BINARY, +}; + +type negation = enum { + NONE, + SPACE, + PLUS, +}; + +type padding = enum { + ALIGN_RIGHT, + ALIGN_LEFT, + ZEROES, +}; + +type modifiers = struct { + padding: padding, + negation: negation, + width: uint, + precision: uint, + base: base, +}; + // Formats text for printing and writes it to an [io::stream]. export fn fprintf( s: *io::stream, @@ -51,29 +104,75 @@ export fn fprintf( r: rune => r, }; + let mod = modifiers { ... }; const arg = switch (r) { - ':' => abort(), // TODO: format modifiers + '{' => match (io::write(s, utf8::encode_rune('{'))) { + err: io::error => return err, + w: size => { + n += w; + continue; + }, + }, '}' => { i += 1z; args[i - 1z]; }, - * => args[scan_uint(r, &iter)], + ':' => { + scan_modifiers(&iter, &mod); + i += 1z; + args[i - 1z]; + }, + * => args[scan_uint(r, &iter)], }; - format(s, arg); - } else { - match (io::write(s, utf8::encode_rune(r))) { + format(s, arg, &mod); + } else if (r == '}') { + match (strings::next(&iter)) { + void => abort("Invalid format string (hanging '}')"), + r: rune => assert(r == '}', "Invalid format string (hanging '}')"), + }; + + match (io::write(s, utf8::encode_rune('}'))) { err: io::error => return err, w: size => { n += w; }, }; + } else match (io::write(s, utf8::encode_rune(r))) { + err: io::error => return err, + w: size => { + n += w; + }, }; }; return n; }; +fn format( + out: *io::stream, + arg: formattable, + mod: *modifiers, +) void = match (arg) { + s: str => io::write(out, strings::to_utf8(s)), + r: rune => io::write(out, utf8::encode_rune(r)), + n: types::numeric => { + let s = strconv::numerictos(n); + io::write(out, strings::to_utf8(s)); + }, + p: uintptr => { + let s = strconv::uptrtos(p); + io::write(out, strings::to_utf8(s)); + }, + v: nullable *void => match (v) { + v: *void => { + let mod = modifiers { base = base::LOWER_HEX, ... }; + format(out, v: uintptr, &mod); + }, + null => format(out, "(null)", mod), + }, +}; + fn scan_uint(r: rune, iter: *strings::iterator) uint = { assert(ascii::isdigit(r)); let num = alloc([]u8, [r: u32: u8], 1z); // XXX: alloc slice w/o cap @@ -83,6 +182,7 @@ fn scan_uint(r: rune, iter: *strings::iterator) uint = { void => abort("Invalid format string (unterminated '(')"), r: rune => r, }; + switch (r) { * => append(num, r: u32: u8), ':' => abort(), // TODO: Format modifiers @@ -96,19 +196,6 @@ fn scan_uint(r: rune, iter: *strings::iterator) uint = { abort("unreachable"); }; -fn format(out: *io::stream, arg: formattable) void = match (arg) { - s: str => io::write(out, strings::to_utf8(s)), - r: rune => io::write(out, utf8::encode_rune(r)), - n: types::numeric => { - let s = strconv::numerictos(n); - io::write(out, strings::to_utf8(s)); - }, - p: uintptr => { - let s = strconv::uptrtos(p); - io::write(out, strings::to_utf8(s)); - }, - v: nullable *void => match (v) { - v: *void => format(out, v: uintptr), // TODO: Hexadecimal - null => format(out, "(null)"), - }, +fn scan_modifiers(iter: *strings::iterator, mod: *modifiers) void = { + abort(); // TODO };