hare

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

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(&copy, 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 };