fmt.ha (17012B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use ascii; 5 use encoding::utf8; 6 use io; 7 use math; 8 use memio; 9 use os; 10 use strconv; 11 use strings; 12 use types; 13 14 // Tagged union of the [[formattable]] types and [[mods]]. Used for 15 // functions which accept format strings. 16 export type field = (...formattable | *mods); 17 18 // Tagged union of all types which are formattable. 19 export type formattable = (...types::numeric | uintptr | str | rune | bool | 20 nullable *opaque | void); 21 22 // Formats text for printing and writes it to [[os::stdout]]. 23 export fn printf(fmt: str, args: field...) (size | io::error) = 24 fprintf(os::stdout, fmt, args...); 25 26 // Formats text for printing and writes it to [[os::stdout]], followed by a line 27 // feed. 28 export fn printfln(fmt: str, args: field...) (size | io::error) = 29 fprintfln(os::stdout, fmt, args...); 30 31 // Formats text for printing and writes it to [[os::stderr]]. 32 export fn errorf(fmt: str, args: field...) (size | io::error) = 33 fprintf(os::stderr, fmt, args...); 34 35 // Formats text for printing and writes it to [[os::stderr]], followed by a line 36 // feed. 37 export fn errorfln(fmt: str, args: field...) (size | io::error) = 38 fprintfln(os::stderr, fmt, args...); 39 40 // Formats text for printing and writes it into a heap-allocated string. The 41 // caller must free the return value. 42 export fn asprintf(fmt: str, args: field...) str = { 43 let buf = memio::dynamic(); 44 assert(fprintf(&buf, fmt, args...) is size); 45 return strings::fromutf8_unsafe(memio::buffer(&buf)); 46 }; 47 48 // Formats text for printing and writes it into a caller supplied buffer. The 49 // returned string is borrowed from this buffer. Aborts if the buffer isn't 50 // large enough to hold the formatted text. 51 export fn bsprintf(buf: []u8, fmt: str, args: field...) str = { 52 let sink = memio::fixed(buf); 53 let l = fprintf(&sink, fmt, args...)!; 54 return strings::fromutf8_unsafe(buf[..l]); 55 }; 56 57 // Formats text for printing and writes it to [[os::stderr]], followed by a line 58 // feed, then exits the program with an error status. 59 export fn fatalf(fmt: str, args: field...) never = { 60 fprintfln(os::stderr, fmt, args...)!; 61 os::exit(255); 62 }; 63 64 // Formats values for printing using the default format modifiers and writes 65 // them to [[os::stderr]] separated by spaces and followed by a line feed, then 66 // exits the program with an error status. 67 export fn fatal(args: formattable...) never = { 68 fprintln(os::stderr, args...)!; 69 os::exit(255); 70 }; 71 72 // Formats text for printing and writes it to an [[io::handle]], followed by a 73 // line feed. 74 export fn fprintfln( 75 h: io::handle, 76 fmt: str, 77 args: field... 78 ) (size | io::error) = { 79 return fprintf(h, fmt, args...)? + io::write(h, ['\n'])?; 80 }; 81 82 // Formats values for printing using the default format modifiers and writes 83 // them to [[os::stdout]] separated by spaces. 84 export fn print(args: formattable...) (size | io::error) = 85 fprint(os::stdout, args...); 86 87 // Formats values for printing using the default format modifiers and writes 88 // them to [[os::stdout]] separated by spaces and followed by a line feed. 89 export fn println(args: formattable...) (size | io::error) = 90 fprintln(os::stdout, args...); 91 92 // Formats values for printing using the default format modifiers and writes 93 // them to [[os::stderr]] separated by spaces. 94 export fn error(args: formattable...) (size | io::error) = 95 fprint(os::stderr, args...); 96 97 // Formats values for printing using the default format modifiers and writes 98 // them to [[os::stderr]] separated by spaces and followed by a line feed. 99 export fn errorln(args: formattable...) (size | io::error) = 100 fprintln(os::stderr, args...); 101 102 // Formats values for printing using the default format modifiers and writes 103 // them into a heap-allocated string separated by spaces. The caller must free 104 // the return value. 105 export fn asprint(args: formattable...) str = { 106 let buf = memio::dynamic(); 107 assert(fprint(&buf, args...) is size); 108 return strings::fromutf8_unsafe(memio::buffer(&buf)); 109 }; 110 111 // Formats values for printing using the default format modifiers and writes 112 // them into a caller supplied buffer separated by spaces. The returned string 113 // is borrowed from this buffer. Aborts if the buffer isn't large enough to hold 114 // the formatted text. 115 export fn bsprint(buf: []u8, args: formattable...) str = { 116 let sink = memio::fixed(buf); 117 let l = fprint(&sink, args...)!; 118 return strings::fromutf8_unsafe(buf[..l]); 119 }; 120 121 // Formats values for printing using the default format modifiers and writes 122 // them to an [[io::handle]] separated by spaces and followed by a line feed. 123 export fn fprintln(h: io::handle, args: formattable...) (size | io::error) = { 124 return fprint(h, args...)? + io::write(h, ['\n'])?; 125 }; 126 127 // Formats values for printing using the default format modifiers and writes 128 // them to an [[io::handle]] separated by spaces. 129 export fn fprint(h: io::handle, args: formattable...) (size | io::error) = { 130 let mod = mods { base = strconv::base::DEC, ... }; 131 let n = 0z; 132 for (let i = 0z; i < len(args); i += 1) { 133 n += format(h, args[i], &mod)?; 134 if (i != len(args) - 1) { 135 n += io::write(h, [' '])?; 136 }; 137 }; 138 return n; 139 }; 140 141 // Specifies for numerical arguments when to prepend a plus or minus sign or a 142 // blank space. 143 export type negation = enum { 144 NONE, 145 SPACE, 146 PLUS, 147 }; 148 149 // Specifies how to align and pad an argument within a given width. 150 export type padding = enum { 151 ALIGN_RIGHT, 152 ALIGN_LEFT, 153 ZEROES, 154 }; 155 156 // Specifies how to format an argument. 157 export type mods = struct { 158 padding: padding, 159 negation: negation, 160 width: uint, 161 precision: uint, 162 base: strconv::base, 163 }; 164 165 type modflag = enum uint { 166 NONE = 0, 167 ZERO = 1 << 0, 168 MINUS = 1 << 1, 169 SPACE = 1 << 2, 170 PLUS = 1 << 3, 171 }; 172 173 type paramindex = (uint | nextparam | void); 174 175 type nextparam = void; 176 177 // Formats text for printing and writes it to an [[io::handle]]. 178 export fn fprintf( 179 h: io::handle, 180 fmt: str, 181 args: field... 182 ) (size | io::error) = { 183 let n = 0z, i = 0z; 184 let checkunused = true; 185 let iter = strings::iter(fmt); 186 for (true) { 187 let r: rune = match (strings::next(&iter)) { 188 case void => 189 break; 190 case let r: rune => 191 yield r; 192 }; 193 194 if (r == '{') { 195 r = match (strings::next(&iter)) { 196 case void => 197 abort("Invalid format string (unterminated '{')"); 198 case let r: rune => 199 yield r; 200 }; 201 202 if (r == '{') { 203 n += io::write(h, utf8::encoderune('{'))?; 204 continue; 205 }; 206 207 const idx = if (ascii::isdigit(r)) { 208 strings::prev(&iter); 209 checkunused = false; 210 yield scan_uint(&iter): size; 211 } else { 212 strings::prev(&iter); 213 i += 1; 214 yield i - 1; 215 }; 216 assert(idx < len(args), "Not enough parameters given"); 217 218 const arg = match (args[idx]) { 219 case let arg: formattable => 220 yield arg; 221 case => 222 abort("Invalid formattable"); 223 }; 224 225 r = match (strings::next(&iter)) { 226 case void => 227 abort("Invalid format string (unterminated '{')"); 228 case let r: rune => 229 yield r; 230 }; 231 232 let mod = &mods { ... }; 233 let pi: paramindex = void; 234 switch (r) { 235 case ':' => 236 scan_inline_modifiers(&iter, mod); 237 case '%' => 238 scan_parametric_modifiers(&iter, &pi); 239 case '}' => void; 240 case => 241 abort("Invalid format string"); 242 }; 243 244 match (pi) { 245 case let pi: uint => 246 checkunused = false; 247 match (args[pi]) { 248 case let pmod: *mods => 249 mod = pmod; 250 case => 251 abort("Explicit parameter is not *fmt::mods"); 252 }; 253 case nextparam => 254 i += 1; 255 match (args[i - 1]) { 256 case let pmod: *mods => 257 mod = pmod; 258 case => 259 abort("Implicit parameter is not *fmt::mods"); 260 }; 261 case void => void; 262 }; 263 264 n += format(h, arg, mod)?; 265 } else if (r == '}') { 266 match (strings::next(&iter)) { 267 case void => 268 abort("Invalid format string (hanging '}')"); 269 case let r: rune => 270 assert(r == '}', "Invalid format string (hanging '}')"); 271 }; 272 273 n += io::write(h, utf8::encoderune('}'))?; 274 } else { 275 n += io::write(h, utf8::encoderune(r))?; 276 }; 277 }; 278 279 assert(!checkunused || i == len(args), "Too many parameters given"); 280 return n; 281 }; 282 283 fn format( 284 out: io::handle, 285 arg: formattable, 286 mod: *mods, 287 ) (size | io::error) = { 288 let z = 0z; 289 290 let reprlen = 0z; 291 if (mod.padding == padding::ALIGN_LEFT) { 292 reprlen = format_raw(out, arg, mod, 0, [])?; 293 z += reprlen; 294 } else { 295 reprlen = format_raw(io::empty, arg, mod, 0, [])?; 296 }; 297 298 let padlen = 0z; 299 let pad: []u8 = []; 300 301 if (reprlen < mod.width: size) { 302 padlen = mod.width: size - reprlen; 303 pad = utf8::encoderune(switch (mod.padding) { 304 case padding::ZEROES => 305 yield '0'; 306 case => 307 yield ' '; 308 }); 309 }; 310 311 if (arg is types::numeric && mod.padding == padding::ZEROES) { 312 // if padlen != 0, then inner padding will be applied 313 z += format_raw(out, arg, mod, padlen, pad)?; 314 } 315 else { 316 for (let i = 0z; i < padlen) { 317 i += io::write(out, pad)?; 318 }; 319 z += padlen; 320 if (mod.padding != padding::ALIGN_LEFT) { 321 z += format_raw(out, arg, mod, 0, [])?; 322 }; 323 }; 324 325 return z; 326 }; 327 328 fn format_raw( 329 out: io::handle, 330 arg: formattable, 331 mod: *mods, 332 padlen: size, 333 pad: []u8, 334 ) (size | io::error) = { 335 match (arg) { 336 case let s: str => 337 return io::write(out, strings::toutf8(s)); 338 case let r: rune => 339 return io::write(out, utf8::encoderune(r)); 340 case let b: bool => 341 return io::write(out, 342 strings::toutf8(if (b) "true" else "false")); 343 case let n: types::numeric => 344 const (s1, s2) = get_split_number_repr(n, mod); 345 let z = io::write(out, strings::toutf8(s1))?; 346 // apply inner padding if required 347 for (let i = 0z; i < padlen) { 348 i += io::write(out, pad)?; 349 }; 350 z += padlen; 351 z += io::write(out, strings::toutf8(s2))?; 352 return z; 353 case let p: uintptr => 354 const s = strconv::uptrtosb(p, mod.base); 355 return io::write(out, strings::toutf8(s)); 356 case let v: nullable *opaque => 357 match (v) { 358 case let v: *opaque => 359 let z = io::write(out, strings::toutf8("0x"))?; 360 const s = strconv::uptrtosb(v: uintptr, 361 strconv::base::HEX_LOWER); 362 z += io::write(out, strings::toutf8(s))?; 363 return z; 364 case null => 365 return io::write(out, strings::toutf8("(null)")); 366 }; 367 case void => 368 return io::write(out, strings::toutf8("void")); 369 }; 370 }; 371 372 fn get_split_number_repr(n: types::numeric, mod: *mods) (str, str) = { 373 const s = strconv::numerictosb(n, mod.base); 374 if (is_negative(n)) { 375 return ( 376 strings::sub(s, 0, 1), 377 strings::sub(s, 1, strings::end) 378 ); 379 } else { 380 const prefix = switch (mod.negation) { 381 case negation::PLUS => yield "+"; 382 case negation::SPACE => yield " "; 383 case negation::NONE => yield ""; 384 }; 385 return (prefix, s); 386 }; 387 }; 388 389 fn is_negative(n: types::numeric) bool = { 390 match (n) { 391 case let i: types::signed => 392 return math::signi(i) < 0; 393 case let u: types::unsigned => 394 return false; 395 case let f: types::floating => 396 return math::signf(f) < 0; 397 }; 398 }; 399 400 fn scan_uint(iter: *strings::iterator) uint = { 401 let copy = *iter; 402 for (true) { 403 let r = match (strings::next(iter)) { 404 case void => 405 abort("Invalid format string (unterminated '{')"); 406 case let r: rune => 407 yield r; 408 }; 409 410 if (!ascii::isdigit(r)) { 411 strings::prev(iter); 412 match (strconv::stou(strings::slice(©, iter))) { 413 case (strconv::invalid | strconv::overflow) => 414 abort("Invalid format string (invalid index)"); 415 case let u: uint => 416 return u; 417 }; 418 }; 419 }; 420 abort("unreachable"); 421 }; 422 423 fn scan_modifier_flags(iter: *strings::iterator, mod: *mods) void = { 424 let flags = modflag::NONE; 425 426 for (true) { 427 let r = match (strings::next(iter)) { 428 case void => 429 abort("Invalid format string (unterminated '{')"); 430 case let r: rune => 431 yield r; 432 }; 433 434 switch (r) { 435 case '0' => 436 flags |= modflag::ZERO; 437 case '-' => 438 flags |= modflag::MINUS; 439 case ' ' => 440 flags |= modflag::SPACE; 441 case '+' => 442 flags |= modflag::PLUS; 443 case => 444 strings::prev(iter); 445 break; 446 }; 447 }; 448 449 mod.padding = if (flags & modflag::MINUS != 0) 450 padding::ALIGN_LEFT 451 else if (flags & modflag::ZERO != 0) 452 padding::ZEROES 453 else 454 padding::ALIGN_RIGHT; 455 456 mod.negation = if (flags & modflag::PLUS != 0) 457 negation::PLUS 458 else if (flags & modflag::SPACE != 0) 459 negation::SPACE 460 else 461 negation::NONE; 462 }; 463 464 fn scan_modifier_width(iter: *strings::iterator, mod: *mods) void = { 465 let r = match (strings::next(iter)) { 466 case void => 467 abort("Invalid format string (unterminated '{')"); 468 case let r: rune => 469 yield r; 470 }; 471 472 let is_digit = ascii::isdigit(r); 473 strings::prev(iter); 474 475 if (is_digit) { 476 mod.width = scan_uint(iter); 477 }; 478 }; 479 480 fn scan_modifier_precision(iter: *strings::iterator, mod: *mods) void = { 481 let r = match (strings::next(iter)) { 482 case void => 483 abort("Invalid format string (unterminated '{')"); 484 case let r: rune => 485 yield r; 486 }; 487 488 if (r == '.') { 489 mod.precision = scan_uint(iter); 490 } else { 491 strings::prev(iter); 492 }; 493 }; 494 495 fn scan_modifier_base(iter: *strings::iterator, mod: *mods) void = { 496 let r = match (strings::next(iter)) { 497 case void => 498 abort("Invalid format string (unterminated '{')"); 499 case let r: rune => 500 yield r; 501 }; 502 503 switch (r) { 504 case 'x' => 505 mod.base = strconv::base::HEX_LOWER; 506 case 'X' => 507 mod.base = strconv::base::HEX_UPPER; 508 case 'o' => 509 mod.base = strconv::base::OCT; 510 case 'b' => 511 mod.base = strconv::base::BIN; 512 case => 513 strings::prev(iter); 514 }; 515 }; 516 517 fn scan_inline_modifiers(iter: *strings::iterator, mod: *mods) void = { 518 scan_modifier_flags(iter, mod); 519 scan_modifier_width(iter, mod); 520 scan_modifier_precision(iter, mod); 521 scan_modifier_base(iter, mod); 522 523 // eat '}' 524 let terminated = match (strings::next(iter)) { 525 case void => 526 yield false; 527 case let r: rune => 528 yield r == '}'; 529 }; 530 assert(terminated, "Invalid format string (unterminated '{')"); 531 }; 532 533 fn scan_parameter_index(iter: *strings::iterator, pi: *paramindex) void = { 534 let r = match (strings::next(iter)) { 535 case void => 536 abort("Invalid format string (unterminated '{')"); 537 case let r: rune => 538 yield r; 539 }; 540 541 let is_digit = ascii::isdigit(r); 542 strings::prev(iter); 543 if (is_digit) { 544 *pi = scan_uint(iter); 545 } else { 546 *pi = nextparam; 547 }; 548 }; 549 550 fn scan_parametric_modifiers(iter: *strings::iterator, pi: *paramindex) void = { 551 scan_parameter_index(iter, pi); 552 553 // eat '}' 554 let terminated = match (strings::next(iter)) { 555 case void => 556 yield false; 557 case let r: rune => 558 yield r == '}'; 559 }; 560 assert(terminated, "Invalid format string (unterminated '{')"); 561 }; 562 563 @test fn fmt() void = { 564 let buf: [1024]u8 = [0...]; 565 566 assert(bsprint(buf, "hello world") == "hello world"); 567 assert(bsprintf(buf, "hello world") == "hello world"); 568 assert(bsprintf(buf, "{} {}", "hello", "world") == "hello world"); 569 assert(bsprintf(buf, "{0} {1}", "hello", "world") == "hello world"); 570 assert(bsprintf(buf, "{0} {0}", "hello", "world") == "hello hello"); 571 assert(bsprintf(buf, "{1} {0} {1}", "hello", "world") == "world hello world"); 572 573 const mod = &mods { width = 7, ... }; 574 assert(bsprintf(buf, "{%}", "hello", mod) == " hello"); 575 assert(bsprintf(buf, "{%1}", "hello", mod) == " hello"); 576 assert(bsprintf(buf, "{0%1}", "hello", mod) == " hello"); 577 assert(bsprintf(buf, "{0%2}", "hello", 0, mod) == " hello"); 578 assert(bsprintf(buf, "{1%2}", 0, "hello", mod) == " hello"); 579 assert(bsprintf(buf, "{2%0}", mod, 0, "hello") == " hello"); 580 assert(bsprintf(buf, "{2%}", mod, 0, "hello") == " hello"); 581 assert(bsprintf(buf, "|{1%}|{}|", mod, "hello") == "| hello|hello|"); 582 assert(bsprintf(buf, "|{}|{2%}|", "hello", mod, "world") == "|hello| world|"); 583 assert(bsprintf(buf, "|{%}|{%}|{%}|{%}|", 584 "hello", &mods { ... }, 585 "world", &mods { width = 10, ... }, 586 123, &mods { width = 10, padding = padding::ZEROES, ... }, 587 0xBEEF, &mods { base = strconv::base::HEX, ... }, 588 ) == "|hello| world|0000000123|BEEF|"); 589 assert(bsprintf(buf, "|{%}|{%}|{0%1}|", 590 "hello", &mods { ... }, 591 "world", &mods { ... }, 592 ) == "|hello|world|hello|"); 593 594 assert(bsprintf(buf, "x: {:8X}", 0xBEEF) == "x: BEEF"); 595 assert(bsprintf(buf, "x: {:8X}", -0xBEEF) == "x: -BEEF"); 596 assert(bsprintf(buf, "x: {:+8X}", 0xBEEF) == "x: +BEEF"); 597 assert(bsprintf(buf, "x: {: 8X}", 0xBEEF) == "x: BEEF"); 598 599 assert(bsprintf(buf, "x: {:+ 8X}", 0xBEEF) == "x: +BEEF"); 600 601 assert(bsprintf(buf, "x: {:-8X}", 0xBEEF) == "x: BEEF "); 602 assert(bsprintf(buf, "x: {:-8X}", -0xBEEF) == "x: -BEEF "); 603 assert(bsprintf(buf, "x: {:-+8X}", 0xBEEF) == "x: +BEEF "); 604 assert(bsprintf(buf, "x: {:- 8X}", 0xBEEF) == "x: BEEF "); 605 606 assert(bsprintf(buf, "x: {:08x}", 0xBEEF) == "x: 0000beef"); 607 assert(bsprintf(buf, "x: {:08x}", -0xBEEF) == "x: -000beef"); 608 assert(bsprintf(buf, "x: {:+08x}", 0xBEEF) == "x: +000beef"); 609 assert(bsprintf(buf, "x: {: 08x}", 0xBEEF) == "x: 000beef"); 610 611 assert(bsprintf(buf, "x: {:-08X}", 0xBEEF) == "x: BEEF "); 612 613 assert(bsprintf(buf, "x: {:o}", 0o755) == "x: 755"); 614 assert(bsprintf(buf, "x: {:b}", 0b11011) == "x: 11011"); 615 616 assert(bsprintf(buf, "x: {:8}", "hello") == "x: hello"); 617 assert(bsprintf(buf, "x: {:-8}", "hello") == "x: hello "); 618 assert(bsprintf(buf, "x: {:08}", "hello") == "x: 000hello"); 619 620 assert(bsprintf(buf, "{} {} {} {} {}", true, false, null, 'x', void) 621 == "true false (null) x void"); 622 };