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:
M | fmt/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
};