print.ha (3668B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use encoding::utf8; 5 use io; 6 use math; 7 use strconv; 8 use strings; 9 use types; 10 11 // Formats values for printing using the default format modifiers and writes 12 // them to an [[io::handle]] separated by spaces. 13 export fn fprint(h: io::handle, args: formattable...) (io::error | size) = { 14 let mod = mods { ... }; 15 let n = 0z; 16 for (let i = 0z; i < len(args); i += 1) { 17 n += format(h, args[i], &mod)?; 18 if (i != len(args) - 1) { 19 n += format(h, " ", &mod)?; 20 }; 21 }; 22 return n; 23 }; 24 25 // Formats text for printing and writes it to an [[io::handle]]. 26 export fn fprintf( 27 h: io::handle, 28 fmt: str, 29 args: field... 30 ) (io::error | size) = { 31 let n = 0z, i = 0z; 32 let it = iter(fmt, args); 33 for (true) match (next(&it)) { 34 case done => break; 35 case let s: str => 36 n += format(h, s, &mods { ... })?; 37 case let f: (formattable, mods) => 38 n += format(h, f.0, &f.1)?; 39 }; 40 41 assert(!it.checkunused || it.idx == len(args), "Too many parameters given"); 42 return n; 43 }; 44 45 fn format( 46 out: io::handle, 47 arg: formattable, 48 mod: *mods, 49 ) (size | io::error) = { 50 let start = 0z; 51 // guaranteed not to have starting padding in either of these cases 52 // saves the extra format_raw() 53 if (mod.width > 0 && mod.alignment != alignment::LEFT) { 54 let width = format_raw(io::empty, arg, mod)?; 55 let pad = if (width > mod.width) 0z else mod.width - width; 56 57 switch (mod.alignment) { 58 case alignment::LEFT => abort(); 59 case alignment::CENTER => start = (pad + 1) / 2; 60 case alignment::RIGHT => start = pad; 61 }; 62 }; 63 64 let z = 0z; 65 for (z < start) { 66 z += io::write(out, utf8::encoderune(mod.pad))?; 67 }; 68 z += format_raw(out, arg, mod)?; 69 for (z < mod.width) { 70 z += io::write(out, utf8::encoderune(mod.pad))?; 71 }; 72 73 return z; 74 }; 75 76 fn format_raw( 77 out: io::handle, 78 arg: formattable, 79 mod: *mods, 80 ) (size | io::error) = match (arg) { 81 case void => 82 return io::write(out, strings::toutf8("void")); 83 case let r: rune => 84 return io::write(out, utf8::encoderune(r)); 85 case let s: str => 86 if (mod.prec > 0 && mod.prec < len(s)) { 87 s = strings::sub(s, 0, mod.prec); 88 }; 89 return io::write(out, strings::toutf8(s)); 90 case let b: bool => 91 return io::write(out, strings::toutf8(if (b) "true" else "false")); 92 case let p: uintptr => 93 const s = strconv::uptrtos(p, mod.base); 94 return io::write(out, strings::toutf8(s)); 95 case let v: nullable *opaque => 96 match (v) { 97 case let v: *opaque => 98 let z = io::write(out, strings::toutf8("0x"))?; 99 const s = strconv::uptrtos(v: uintptr, strconv::base::HEX_LOWER); 100 z += io::write(out, strings::toutf8(s))?; 101 return z; 102 case null => 103 return io::write(out, strings::toutf8("(null)")); 104 }; 105 case let f: types::floating => 106 assert(mod.base == strconv::base::DEFAULT 107 || mod.base == strconv::base::DEC); // TODO 108 assert(mod.prec <= types::UINT_MAX); 109 // TODO: mod.prec should be (size | void) but that needs @default 110 return strconv::fftosf(out, f, mod.ffmt, 111 if (mod.prec != 0) mod.prec: uint else void, mod.fflags)?; 112 case let i: types::integer => 113 let neg = match (i) { 114 case types::unsigned => 115 yield false; 116 case let s: types::signed => 117 i = math::absi(s); 118 yield math::signi(s) < 0; 119 }; 120 let sign = if (neg) "-" else { 121 yield switch (mod.neg) { 122 case neg::PLUS => yield "+"; 123 case neg::SPACE => yield " "; 124 case neg::NONE => yield ""; 125 }; 126 }; 127 128 let i = strconv::integertos(i, mod.base); 129 let pad = if (mod.prec < len(sign) + len(i)) { 130 yield 0z; 131 } else { 132 yield mod.prec - len(sign) - len(i); 133 }; 134 135 let z = io::write(out, strings::toutf8(sign))?; 136 for (let i = 0z; i < pad) { 137 i += io::write(out, ['0'])?; 138 }; 139 z += pad; 140 return z + io::write(out, strings::toutf8(i))?; 141 };