MT Core (C++)
Core library for replacing C++ standard in project usage
Loading...
Searching...
No Matches
formats.hpp
Go to the documentation of this file.
1/*
2
3Copyright 2025 Matthew Tolman
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17*/
18
19#ifndef MTCORE_FORMATS_HPP
20#define MTCORE_FORMATS_HPP
21
22#include "mtcore/alloc.hpp"
23#include "mtcore/ascii.hpp"
25#include "floats.hpp"
26#include "format.hpp"
27#include "writer.hpp"
28#include <cstdio>
29
30namespace mtcore::io {
40
51
72 if (formatStr.empty()) {
73 return nullopt;
74 }
75 auto semiColon = slices::first_index_not_proceeded_by('\\', ';', formatStr);
76 if (!semiColon.has_value()) {
77 return nullopt;
78 }
79
80 auto checkWriter = void_writer<char>();
81 // This checks if we have an effective single character
82 auto has_single_char = [&checkWriter](Slice<const char> s) {
83 auto res = ascii::write_unescaped(checkWriter, s);
84 // void writers don't error out, so only error is a bad format string
85 ensure(res.is_success(), "BAD FORMAT STRING!");
86 return res.value() == 1;
87 };
88
89 auto alignmentSearchSpace = formatStr.sub(0, semiColon.value());
90 auto leftPadLoc = slices::first_index_not_proceeded_by('\\', '<', alignmentSearchSpace);
91 if (leftPadLoc.has_value() && leftPadLoc.value() > 0) {
92 auto n = alignmentSearchSpace.sub(leftPadLoc.value() + 1);
93 if (!n.empty() && ascii::is_pos_int_str(n)) {
94 auto pad = formatStr.sub(0, leftPadLoc.value());
95 if (has_single_char(pad)) {
96 auto len = ascii::base10_to_int<size_t>(n).value();
97 return PaddingOptions{
98 .alignment = ContentAlignment::LEFT,
99 .padLen = len,
100 .pad = ascii::unescape_char(pad).value(),
101 .endPos = semiColon.value(),
102 };
103 }
104 }
105 }
106
107 auto rightPadLoc = slices::first_index_not_proceeded_by('\\', '>', alignmentSearchSpace);
108 if (rightPadLoc.has_value() && rightPadLoc.value() > 0) {
109 auto n = alignmentSearchSpace.sub(rightPadLoc.value() + 1);
110 if (!n.empty() && ascii::is_pos_int_str(n)) {
111 auto pad = formatStr.sub(0, rightPadLoc.value());
112 if (has_single_char(pad)) {
113 auto len = ascii::base10_to_int<size_t>(n).value();
114 return PaddingOptions{
115 .alignment = ContentAlignment::RIGHT,
116 .padLen = len,
117 .pad = ascii::unescape_char(pad).value(),
118 .endPos = semiColon.value(),
119 };
120 }
121 }
122 }
123
124 auto centerPadLoc = slices::first_index_not_proceeded_by('\\', '^', alignmentSearchSpace);
125 if (centerPadLoc.has_value() && centerPadLoc.value() > 0) {
126 auto n = alignmentSearchSpace.sub(centerPadLoc.value() + 1);
127 if (!n.empty() && ascii::is_pos_int_str(n)) {
128 auto pad = formatStr.sub(0, centerPadLoc.value());
129 if (has_single_char(pad)) {
130 auto len = ascii::base10_to_int<size_t>(n).value();
131 return PaddingOptions{
132 .alignment = ContentAlignment::CENTER,
133 .padLen = len,
134 .pad = ascii::unescape_char(pad).value(),
135 .endPos = semiColon.value(),
136 };
137 }
138 }
139 }
140
141 return nullopt;
142 }
143
152 template<typename T>
153 struct Padded {
154 template<WriterImpl WI>
156 const FormatOptions &opts, const T &elem);
157 };
158
173 template<>
174 struct Formatter<char> {
175 template<WriterImpl WI>
177 const char &ch);
178 };
179
194 template<std::integral T>
195 struct Formatter<T> {
196 static constexpr auto digitTable = std::array{
197 static_cast<i64>(1ll),
198 static_cast<i64>(10ll),
199 static_cast<i64>(100ll),
200 static_cast<i64>(1000ll),
201 static_cast<i64>(10000ll),
202 static_cast<i64>(100000ll),
203 static_cast<i64>(1000000ll),
204 static_cast<i64>(10000000ll),
205 static_cast<i64>(100000000ll),
206 static_cast<i64>(1000000000ll),
207 static_cast<i64>(10000000000ll),
208 static_cast<i64>(100000000000ll),
209 static_cast<i64>(1000000000000ll),
210 static_cast<i64>(10000000000000ll),
211 static_cast<i64>(100000000000000ll),
212 static_cast<i64>(1000000000000000ll),
213 static_cast<i64>(10000000000000000ll),
214 static_cast<i64>(100000000000000000ll),
215 static_cast<i64>(1000000000000000000ll),
216 };
217
218 template<WriterImpl WI>
220 const T &val) {
221 auto padOpts = extract_padding_options(opts.formatStr);
222 if (padOpts.has_value()) {
223 return Padded<T>::write_padded(writer, padOpts.value(), {opts.formatStr.sub(padOpts->endPos + 1)}, val);
224 }
225
226 size_t written = 0;
227
228 u64 uval;
229 if constexpr (std::is_signed_v<T>) {
230 // We want to copy cast the bits without changing the value
231 // we also want to 0 fill the sign bit when casting up, that way we get the right hex/binary
232 // without a lot of extra padding
233 auto tmp = val;
234 auto tmpPtr = reinterpret_cast<std::make_unsigned_t<T> *>(&tmp);
235 uval = *tmpPtr;
236 }
237 else {
238 uval = val;
239 }
240
241 if (str_equal(opts.formatStr, "X")) {
242 static constexpr auto chars = slice_from("0123456789ABCDEF");
243
244 auto hexChars = std::array<char, 16>{
245 static_cast<char>(chars[(uval >> 60) & 0xfull]), static_cast<char>(chars[(uval >> 56) & 0xfull]),
246 static_cast<char>(chars[(uval >> 52) & 0xfull]), static_cast<char>(chars[(uval >> 48) & 0xfull]),
247 static_cast<char>(chars[(uval >> 44) & 0xfull]), static_cast<char>(chars[(uval >> 40) & 0xfull]),
248 static_cast<char>(chars[(uval >> 36) & 0xfull]), static_cast<char>(chars[(uval >> 32) & 0xfull]),
249 static_cast<char>(chars[(uval >> 28) & 0xfull]), static_cast<char>(chars[(uval >> 24) & 0xfull]),
250 static_cast<char>(chars[(uval >> 20) & 0xfull]), static_cast<char>(chars[(uval >> 16) & 0xfull]),
251 static_cast<char>(chars[(uval >> 12) & 0xfull]), static_cast<char>(chars[(uval >> 8) & 0xfull]),
252 static_cast<char>(chars[(uval >> 4) & 0xfull]), static_cast<char>(chars[(uval >> 0) & 0xfull]),
253 };
254
255 auto hex = slice_from(hexChars);
256 auto start = slices::first_index_not('0', hex);
257
258 if (start.has_value()) {
259 auto out = hex.sub(start.value());
260 written += out.size();
261 auto res = writer.write_all(out);
262 return res.with_success_val(written);
263 }
264 else {
265 ++written;
266 return writer.write('0').with_success_val(written);
267 }
268 }
269
270 if (str_equal(opts.formatStr, "x")) {
271 static constexpr auto chars = slice_from("0123456789abcdef");
272
273 auto hexChars = std::array<char, 16>{
274 static_cast<char>(chars[(uval >> 60) & 0xfull]), static_cast<char>(chars[(uval >> 56) & 0xfull]),
275 static_cast<char>(chars[(uval >> 52) & 0xfull]), static_cast<char>(chars[(uval >> 48) & 0xfull]),
276 static_cast<char>(chars[(uval >> 44) & 0xfull]), static_cast<char>(chars[(uval >> 40) & 0xfull]),
277 static_cast<char>(chars[(uval >> 36) & 0xfull]), static_cast<char>(chars[(uval >> 32) & 0xfull]),
278 static_cast<char>(chars[(uval >> 28) & 0xfull]), static_cast<char>(chars[(uval >> 24) & 0xfull]),
279 static_cast<char>(chars[(uval >> 20) & 0xfull]), static_cast<char>(chars[(uval >> 16) & 0xfull]),
280 static_cast<char>(chars[(uval >> 12) & 0xfull]), static_cast<char>(chars[(uval >> 8) & 0xfull]),
281 static_cast<char>(chars[(uval >> 4) & 0xfull]), static_cast<char>(chars[(uval >> 0) & 0xfull]),
282 };
283
284 auto hex = slice_from(hexChars);
285 auto start = slices::first_index_not('0', hex);
286
287 if (start.has_value()) {
288 auto out = hex.sub(start.value());
289 written += out.size();
290 auto res = writer.write_all(out);
291 return res.with_success_val(written);
292 }
293 else {
294 ++written;
295 return writer.write('0').with_success_val(written);
296 }
297 }
298
299 if (str_equal(opts.formatStr, "o")) {
300 static constexpr auto chars = slice_from("01234567");
301
302 auto hexChars = std::array<char, 22>{
303 static_cast<char>(chars[(uval >> 63) & 0x1ull]), static_cast<char>(chars[(uval >> 60) & 0x7ull]),
304 static_cast<char>(chars[(uval >> 57) & 0x7ull]), static_cast<char>(chars[(uval >> 54) & 0x7ull]),
305 static_cast<char>(chars[(uval >> 51) & 0x7ull]), static_cast<char>(chars[(uval >> 48) & 0x7ull]),
306 static_cast<char>(chars[(uval >> 45) & 0x7ull]), static_cast<char>(chars[(uval >> 42) & 0x7ull]),
307 static_cast<char>(chars[(uval >> 39) & 0x7ull]), static_cast<char>(chars[(uval >> 36) & 0x7ull]),
308 static_cast<char>(chars[(uval >> 33) & 0x7ull]), static_cast<char>(chars[(uval >> 30) & 0x7ull]),
309 static_cast<char>(chars[(uval >> 27) & 0x7ull]), static_cast<char>(chars[(uval >> 24) & 0x7ull]),
310 static_cast<char>(chars[(uval >> 21) & 0x7ull]), static_cast<char>(chars[(uval >> 18) & 0x7ull]),
311 static_cast<char>(chars[(uval >> 15) & 0x7ull]), static_cast<char>(chars[(uval >> 12) & 0x7ull]),
312 static_cast<char>(chars[(uval >> 9) & 0x7ull]), static_cast<char>(chars[(uval >> 6) & 0x7ull]),
313 static_cast<char>(chars[(uval >> 3) & 0x7ull]), static_cast<char>(chars[(uval >> 0) & 0x7ull]),
314 };
315
316 auto hex = slice_from(hexChars);
317 auto start = slices::first_index_not('0', hex);
318
319 if (start.has_value()) {
320 auto out = hex.sub(start.value());
321 written += out.size();
322 auto res = writer.write_all(out);
323 return res.with_success_val(written);
324 }
325 else {
326 ++written;
327 return writer.write('0').with_success_val(written);
328 }
329 }
330
331 if (str_equal(opts.formatStr, "b")) {
333 bits.init(uval);
334
335 // to print properly to make sense to humans, we actually print "backwards"
336 // This is because high bits are traditionally on the left while low bits are traditionally on the right
337 auto firstBit = bits.first_set();
338 auto lastBit = bits.last_set();
339 if (firstBit.has_value()) {
340 ensure(lastBit.has_value());
341 for (size_t j = lastBit.value() + 1; j > 0; --j) {
342 auto i = j - 1;
343 ensure(i < j);
344 auto chRes = writer.write(bits.at(i) ? '1' : '0');
345 if (chRes.is_error()) {
346 return chRes.error();
347 }
348 ++written;
349 }
350 return written;
351 }
352 else {
353 ++written;
354 return writer.write('0').with_success_val(written);
355 }
356 }
357
358 if constexpr (std::is_signed_v<T>) {
359 if (val < 0) {
360 if (auto signRes = writer.write('-'); signRes.is_error()) {
361 return signRes.error();
362 }
363 written += 1;
364 }
365 }
366
367 if (val == 0) {
368 if (auto zRes = writer.write('0'); zRes.is_error()) {
369 return zRes.error();
370 }
371 written += 1;
372 }
373 else {
374 auto v = val;
375 if (val > 0) {
376 for (auto numDigits = num_digits(val); numDigits > 0; --numDigits) {
377 auto digit = (v / digitTable[numDigits - 1]);
378 ensure(digit >= 0 && digit < 10);
379 if (auto chRes = writer.write(digit + '0'); chRes.is_error()) {
380 return chRes.error();
381 }
382 ++written;
383 v -= digit * digitTable[numDigits - 1];
384 }
385 }
386 else {
387 for (auto numDigits = num_digits(val); numDigits > 0; --numDigits) {
388 auto digit = -(v / digitTable[numDigits - 1]);
389 ensure(digit >= 0 && digit < 10);
390 if (auto chRes = writer.write(digit + '0'); chRes.is_error()) {
391 return chRes.error();
392 }
393 ++written;
394 v += digit * digitTable[numDigits - 1];
395 }
396 }
397 }
398
399 return success(written);
400 }
401
402 static size_t num_digits(const T &val) {
403 if constexpr (std::is_signed_v<T>) {
404 if (val < 0) {
405 const i64 v = val;
406 for (size_t i = 0; i < digitTable.size(); ++i) {
407 if (v > -digitTable[i]) {
408 return i;
409 }
410 }
411 return digitTable.size();
412 }
413 }
414
415 for (size_t i = 0; i < digitTable.size(); ++i) {
416 if (val < static_cast<T>(digitTable[i])) {
417 return i;
418 }
419 }
420 return digitTable.size();
421 }
422 };
423
442 template<std::floating_point T>
443 struct Formatter<T> {
444 template<WriterImpl WI>
446 auto padOpts = extract_padding_options(opts.formatStr);
447 if (padOpts.has_value()) {
448 return Padded<T>::write_padded(writer, padOpts.value(), {opts.formatStr.sub(padOpts->endPos + 1)}, val);
449 }
450 return floats::dragonbox::format_float(writer, static_cast<f64>(val), opts.formatStr);
451 }
452 };
453
483 template<Iterable T>
484 struct Formatter<T> {
485 using ElemType = std::remove_const_t<typename decltype(std::declval<T>().iter())::IterElem>;
486
487 template<WriterImpl WI>
489 size_t maxElems = std::numeric_limits<size_t>::max();
490
491 auto endMaxElemPos = slices::first_index_not_proceeded_by('\\', '!', opts.formatStr);
492 if (endMaxElemPos.has_value()) {
493 auto rng = opts.formatStr.sub(0, endMaxElemPos.value());
494 if (!rng.empty() && ascii::is_pos_int_str(rng)) {
495 maxElems = ascii::base10_to_int(rng).value();
496 opts.formatStr = opts.formatStr.sub(endMaxElemPos.value() + 1);
497 }
498 }
499
500 auto padOpts = extract_padding_options(opts.formatStr);
501 if (padOpts.has_value()) {
502 return Padded<T>::write_padded(writer, padOpts.value(), {opts.formatStr.sub(padOpts->endPos + 1)}, iterable);
503 }
504
505 // default separator
506 auto sep = slice_from(" ");
507 if constexpr (std::is_same_v<ElemType, char>) {
508 if (opts.formatStr.empty()) {
509 sep = slice_from("");
510 }
511 }
512
513 auto sepIndex = slices::first_index_not_proceeded_by('\\', ':', opts.formatStr);
514 if (sepIndex.has_value()) {
515 sep = opts.formatStr.sub(0, sepIndex.value());
516 opts.formatStr = opts.formatStr.sub(sepIndex.value() + 1);
517 }
518
519 auto iter = iterable.iter();
520 ElemType cur;
521 auto first = true;
522 size_t written = 0;
523 size_t elem = 0;
524 while (elem++ < maxElems && iter.next().move_if_present(cur)) {
525 if (first) {
526 first = false;
527 }
528 else {
529 auto sepRes = ascii::write_unescaped(writer, sep);
530 if (sepRes.is_error()) {
531 auto errCode = sepRes.error().code;
532 ensure(!std::holds_alternative<ascii::UnescapeError>(errCode), "BAD FORMAT STRING");
533 return error(std::get<typename Writer<WI>::ErrType>(errCode));
534 }
535 written += sepRes.value();
536 }
537
538 auto fmtSegEnd =
540 auto fullOpts = opts.formatStr;
541 if (fmtSegEnd.has_value()) {
542 opts.formatStr = opts.formatStr.sub(0, fmtSegEnd.value());
543 }
544 mtdefer {
545 if (fmtSegEnd.has_value()) {
546 opts.formatStr = fullOpts.sub(fmtSegEnd.value() + 1);
547 }
548 };
549 auto elemRes = Formatter<ElemType>::fmt(writer, opts, cur);
550 if (elemRes.is_error()) {
551 return elemRes.error();
552 }
553 written += elemRes.value();
554 }
555 return success(written);
556 }
557 };
558
589 template<StdIterable T>
590 struct Formatter<T> {
591 using ElemType = std::remove_reference_t<std::remove_const_t<decltype(*std::begin(std::declval<T &>()))>>;
592
593 template<WriterImpl WI>
595 size_t maxElems = std::numeric_limits<size_t>::max();
596
597 if (!opts.formatStr.empty()) {
598 auto endMaxElemPos = slices::first_index_not_proceeded_by('\\', '!', opts.formatStr);
599 if (endMaxElemPos.has_value()) {
600 auto rng = opts.formatStr.sub(0, endMaxElemPos.value());
601 if (!rng.empty() && ascii::is_pos_int_str(rng)) {
602 maxElems = ascii::base10_to_int(rng).value();
603 opts.formatStr = opts.formatStr.sub(endMaxElemPos.value() + 1);
604 }
605 }
606 }
607
608 auto padOpts = extract_padding_options(opts.formatStr);
609 if (padOpts.has_value()) {
610 return Padded<T>::write_padded(writer, padOpts.value(), {opts.formatStr.sub(padOpts->endPos + 1)}, iterable);
611 }
612
613 // default separator
614 auto sep = slice_from(" ");
615 if constexpr (std::is_same_v<ElemType, char>) {
616 if (opts.formatStr.empty()) {
617 sep = slice_from("");
618 }
619 }
620
621 auto sepIndex = slices::first_index_not_proceeded_by('\\', ':', opts.formatStr);
622 if (sepIndex.has_value()) {
623 sep = opts.formatStr.sub(0, sepIndex.value());
624 opts.formatStr = opts.formatStr.sub(sepIndex.value() + 1);
625 }
626
627 auto first = true;
628 size_t written = 0;
629 size_t elem = 0;
630 for (const auto &cur: iterable) {
631 if (elem++ >= maxElems) {
632 break;
633 }
634 if (first) {
635 first = false;
636 }
637 else {
638 auto sepRes = ascii::write_unescaped(writer, sep);
639 if (sepRes.is_error()) {
640 auto errCode = sepRes.error().code;
641 ensure(!std::holds_alternative<ascii::UnescapeError>(errCode), "BAD FORMAT STRING");
642 return error(std::get<typename Writer<WI>::ErrType>(errCode));
643 }
644 written += sepRes.value();
645 }
646
647 auto fmtSegEnd =
649 auto fullOpts = opts.formatStr;
650 if (fmtSegEnd.has_value()) {
651 opts.formatStr = opts.formatStr.sub(0, fmtSegEnd.value());
652 }
653 mtdefer {
654 if (fmtSegEnd.has_value()) {
655 opts.formatStr = fullOpts.sub(fmtSegEnd.value() + 1);
656 }
657 };
658 auto elemRes = Formatter<ElemType>::fmt(writer, opts, cur);
659 if (elemRes.is_error()) {
660 return elemRes.error();
661 }
662 written += elemRes.value();
663 }
664 return success(written);
665 }
666 };
667
668
675 template<>
676 struct Formatter<char *> {
677 template<WriterImpl WI>
679 return Formatter<Slice<const char>>::fmt(writer, opts, slice_from(ch));
680 }
681 };
682
683
690 template<>
691 struct Formatter<const char *> {
692 template<WriterImpl WI>
694 const char *ch) {
695 return Formatter<Slice<const char>>::fmt(writer, opts, slice_from(ch));
696 }
697 };
698
699 template<WriterImpl WI>
701 const char &ch) {
702 auto padOpts = extract_padding_options(opts.formatStr);
703 if (padOpts.has_value()) {
704 return Padded<char>::write_padded(writer, padOpts.value(), {opts.formatStr.sub(padOpts->endPos + 1)}, ch);
705 }
706
707 if (str_equal(opts.formatStr, "b")) {
708 auto bits = std::array<int, 8>{
709 (ch >> 7) & 0x1, (ch >> 6) & 0x1, (ch >> 5) & 0x1, (ch >> 4) & 0x1,
710 (ch >> 3) & 0x1, (ch >> 2) & 0x1, (ch >> 1) & 0x1, (ch >> 0) & 0x1,
711 };
712 auto bit = slice_from(bits);
713 auto start = slices::first_index(1, bit);
714
715 // write the minimal bits needed
716 if (!start.has_value()) {
717 return Formatter<int>::fmt(writer, {slice_from("d")}, 0);
718 }
719 else {
721 slice_from(bits).sub(start.value()));
722 }
723 }
724 else if (str_equal(opts.formatStr, "X")) {
725 constexpr auto chars = "0123456789ABCDEF";
726
727 auto c = chars[(ch >> 4) & 0xf];
728 // by default, no padding
729 if (c != '0') {
730 auto ch1 = writer.write(c);
731 if (ch1.is_error()) {
732 return ch1.error();
733 }
734 }
735 auto ch2 = writer.write(chars[ch & 0xf]);
736 if (ch2.is_error()) {
737 return ch2.error();
738 }
739 return static_cast<size_t>(c != '0' ? 2 : 1);
740 }
741 else if (str_equal(opts.formatStr, "x")) {
742 constexpr auto chars = "0123456789abcdef";
743 auto c = chars[(ch >> 4) & 0xf];
744 // by default, no padding
745 if (c != '0') {
746 auto ch1 = writer.write(c);
747 if (ch1.is_error()) {
748 return ch1.error();
749 }
750 }
751 auto ch2 = writer.write(chars[ch & 0xf]);
752 if (ch2.is_error()) {
753 return ch2.error();
754 }
755 return static_cast<size_t>(c != '0' ? 2 : 1);
756 }
757 else if (str_equal(opts.formatStr, "o")) {
758 constexpr auto chars = "01234567";
759 auto octalChars = std::array<char, 22>{
760 static_cast<char>(chars[(ch >> 6) & 0x5ull]),
761 static_cast<char>(chars[(ch >> 3) & 0x7ull]),
762 static_cast<char>(chars[(ch >> 0) & 0x7ull]),
763 };
764 auto octal = slice_from(octalChars);
765 auto start = slices::first_index_not('0', octal);
766
767 size_t written = 0;
768 if (start.has_value()) {
769 auto out = octal.sub(start.value());
770 written += out.size();
771 auto res = writer.write_all(out);
772 return res.with_success_val(written);
773 }
774 else {
775 ++written;
776 return writer.write('0').with_success_val(written);
777 }
778 }
779 else if (str_equal(opts.formatStr, "d")) {
780 auto v = static_cast<u8>(ch);
781 return Formatter<u8>::fmt(writer, {slice_from("d")}, v);
782 }
783 else {
784 return writer.write(ch).with_success_val(static_cast<size_t>(1));
785 }
786 }
787
788 template<typename T>
789 template<WriterImpl WI>
791 Padded<T>::write_padded(Writer<WI> &writer, const PaddingOptions &padOpts, const FormatOptions &opts, const T &elem) {
792 // Do a dry run to get what the ending length will be
793 // Doing a dry run since we're so disconnected from the logic
794 // that we can't accurately tell the final length without "printing"
795 auto dryRunWriter = void_writer<typename Writer<WI>::WriteElem>();
796 auto sizeRes = Formatter<T>::fmt(dryRunWriter, opts, elem);
797 ensure(sizeRes.is_success());
798 size_t len = sizeRes.value();
799
800 // If we don't need to pad, just write it directly
801 if (len >= padOpts.padLen) {
802 return Formatter<T>::fmt(writer, opts, elem);
803 }
804
805 switch (padOpts.alignment) {
807 auto padRes = writer.write_n_times(padOpts.pad, padOpts.padLen - len);
808 if (padRes.is_error()) {
809 return padRes.error();
810 }
811 return Formatter<T>::fmt(writer, opts, elem).with_success_val(padOpts.padLen);
812 }
814 auto contentRes = Formatter<T>::fmt(writer, opts, elem);
815 if (contentRes.is_error()) {
816 return contentRes.error();
817 }
818 return writer.write_n_times(padOpts.pad, padOpts.padLen - len).with_success_val(padOpts.padLen);
819 }
821 auto leftPad = (padOpts.padLen - len) / 2;
822 auto rightPad = leftPad;
823
824 // If we have to be lopsided, put the extra padding on the right
825 if (leftPad + rightPad + len != padOpts.padLen) {
826 ++rightPad;
827 }
828 auto padLeftRes = writer.write_n_times(padOpts.pad, leftPad);
829 if (padLeftRes.is_error()) {
830 return padLeftRes.error();
831 }
832 auto contentRes = Formatter<T>::fmt(writer, opts, elem);
833 if (contentRes.is_error()) {
834 return contentRes.error();
835 }
836 return writer.write_n_times(padOpts.pad, rightPad).with_success_val(padOpts.padLen);
837 }
838 default:
839 unreachable();
840 }
841 }
842} // namespace mtcore
843
844#include "mtcore/csv/common.hpp"
845
846namespace mtcore::io {
854 template<typename T>
855 struct Formatter<Optional<T>> {
856 template<WriterImpl WI>
858 if (!opt.has_value()) {
859 return writer.write_all(slice_from("nullopt"));
860 }
861 if constexpr (is_formattable<T>) {
862 return print(writer, "Optional\\{{}\\}", opt.value());
863 }
864 else {
865 return writer.write_all(slice_from("Optional{?}"));
866 }
867 }
868 };
869
879 template<typename V, typename E>
880 struct Formatter<Result<V, E>> {
881 template<WriterImpl WI>
883 if (!res.is_success() || !res.is_error()) {
884 return writer.write_all(slice_from("Result{<MOVED>}"));
885 }
886
887 if (res.is_error()) {
888 if constexpr (is_formattable<E>) {
889 return print(writer, "Result\\{<ERROR> {}\\}", res.error());
890 }
891 else if constexpr (std::is_convertible_v<E, int>) {
892 const auto err = static_cast<int>(res.error());
893 auto typeId = typeid(E).name();
894 return print(writer, "Result\\{<ERROR> {} {}\\}", slice_from(typeId), err);
895 }
896 else {
897 return writer.write_all(slice_from("Result\\{<ERROR>\\}"));
898 }
899 }
900
901 if constexpr (std::is_same_v<void, V>) {
902 return writer.write_all(slice_from("Result{<SUCCESS>}"));
903 }
904 else if constexpr (is_formattable<V>) {
905 return print(writer, "Result\\{<SUCCESS> {}\\}", res.value());
906 }
907 else {
908 return print(writer, "Result\\{<SUCCESS> ?\\}");
909 }
910 }
911 };
912} // namespace mtcore
913
914#include "../traits.hpp"
915
927template<mtcore::Pointer T>
928struct mtcore::io::Formatter<T> {
929 template<WriterImpl WI>
931 if (ptr == nullptr) {
932 return writer.write_all(slice_from("nullptr"));
933 }
934
935 if (str_equal(opts.formatStr, "*")) {
937 return print(writer, "*{}", *ptr);
938 }
939 else {
940 return writer.write_all(slice_from("*?"));
941 }
942 }
943
944 auto addr = reinterpret_cast<intptr_t>(ptr);
945 if (str_equal(opts.formatStr, "x")) {
946 return print(writer, "{x}", addr);
947 }
948 if (str_equal(opts.formatStr, "s")) {
949 return print(writer, "0x{x}", addr);
950 }
951 return print(writer, "Addr 0x{x}", addr);
952 }
953};
954
955template<mtcore::InlineFormattable T>
956struct mtcore::io::Formatter<T> {
957 template<WriterImpl WI>
959 return f.format(writer, opts);
960 }
961};
962
963namespace mtcore::io {
964 template<Formattable WI>
965 struct EnsureFormattable : std::true_type {};
966 static_assert(EnsureFormattable<char>::value);
967}
968
969#endif // MTCORE_FORMATS_HPP
Result< Out, AsciiNumericParseError > base10_to_int(const Slice< const char > &parse)
Tries to parse an ASCII numeric string into an integer Will return an error if the parsed string does...
Definition ascii.hpp:244
bool is_pos_int_str(Slice< const char > str)
Checks if a string is composed of only positive integer characters Will return false if string is emp...
Definition ascii.hpp:123
Result< size_t, std::variant< typename Writer::ErrType, UnescapeError > > write_unescaped(Writer &writer, Slice< const char > str, char escapeChar='\\')
Writes unescaped ASCII characters to a stream Note: This does NOT support writing Unicode escape code...
Definition ascii.hpp:447
Result< char, UnescapeError > unescape_char(Slice< const char > str, char escapeChar='\\')
Unescapes the first (potentially) escaped character in a character sequence If the sequence is empty,...
Definition ascii.hpp:343
constexpr auto nullopt
Placeholder value for an empty Optional.
Definition optional.hpp:409
mtcore::Optional< size_t > first_index_not_proceeded_by(const std::remove_const_t< T > &prefix, const std::remove_const_t< T > &needle, const Slice< T > &haystack)
Gets the first index that a needle appears in the haystack, or nullopt if the needle does not appear ...
constexpr Slice< const char32_t > slice_from(char32_t *cstr)
Creates a slice from a utf32 string in the form of a c string.
mtcore::Optional< size_t > first_index(const Slice< T > &needle, const Slice< T > &haystack)
Gets the first index that a needle appears in the haystack, or nullopt if the needle does not appear ...
mtcore::Optional< size_t > first_index_not(const std::remove_const_t< T > &needle, const Slice< T > &haystack)
Gets the first index that a needle appears in the haystack, or nullopt if the needle does not appear ...
ContentAlignment
Represents content alignment options for padded formatting options.
Definition formats.hpp:35
Optional< PaddingOptions > extract_padding_options(Slice< const char > formatStr)
Extracts padding options from a format string if present Padding options are in the following form: (...
Definition formats.hpp:71
Result< size_t, typename Writer< WI >::ErrType > print(Writer< WI > &writer, const char *fmt, const Args &...args)
Prints arguments using a format string Element insert points are designated by a pair of curly braces...
#define ensure(check,...)
Ensures that a check holds true, aborts the program if not true Will print error if the condition is ...
#define unreachable(...)
Marks code as unreachable.
#define mtdefer
Defer statement that will mtdefer execution until the scope is left, at which point the code will run...
Success< void > success()
Creates a successful void Result object.
Definition result.hpp:398
Error< Underlying > error(Underlying err)
Creates an error.
Definition result.hpp:425
constexpr bool is_formattable
Checks if a type has a zero-parameter deinit.
uint64_t u64
Alias for 64-bit unsigned ints.
int64_t i64
Alias for 64-bit ints.
uint8_t u8
Alias for 8-bit unsigned ints.
double f64
Alias for 64-bit floats.
io::Writer< impl::VoidWriterImpl< T > > void_writer()
Creates a writer which discards what's written (think of it as writer to /dev/null) Useful for doing ...
Result< size_t, typename Writer< WI >::ErrType > format_float(Writer< WI > &writer, f64 f, Slice< const char > opts={nullptr, 0})
Definition floats.hpp:159
Generic iterator defaults built on common contracts Does not guarantee performance of iterators Actua...
Definition iter.hpp:91
bool str_equal(const L &left, const R &right)
Compares two string strings for ordering.
Represents a bitset with dynamically allocated memory (using an mtcore allocator) Allows operating on...
Definition bitset.hpp:826
Optional< size_t > first_set() const
Returns the position of the first set bit (if one is set) Otherwise, returns a null Optional.
Definition bitset.hpp:974
Optional< size_t > last_set() const
Returns the position of the last set bit (if one is set) Otherwise, returns a null Optional.
Definition bitset.hpp:959
bool at(const size_t pos) const
Reads a bit at a specific position.
Definition bitset.hpp:912
void init(u64 val)
Definition bitset.hpp:830
Represents a value that may or may not exist (an "Optional" value) Similar concept to std::optional,...
Definition optional.hpp:235
bool has_value() const noexcept
Definition optional.hpp:294
T & value() noexcept
Definition optional.hpp:301
Represents a Result that may have an error (error code) or a success value A type of "void" means the...
Definition result.hpp:170
T value() const noexcept
Checks if is a successful Result (aka.
Definition result.hpp:258
bool is_success() const noexcept
Checks if is a successful Result (aka.
Definition result.hpp:253
bool is_error() const noexcept
Checks if is an error Result.
Definition result.hpp:266
ErrVal error() const noexcept
Returns the associated error Fails if there is no error;.
Definition result.hpp:294
A Slice which is just a pointer + length Accessing elements through the array operator will do bounds...
constexpr Slice sub(size_t start) const noexcept
Gets a sub Slice from start.
constexpr bool empty() const noexcept
Checks if a Slice is empty.
Options for specifying formatting.
Definition format.hpp:36
Slice< const char > formatStr
Definition format.hpp:37
static Result< size_t, typename Writer< WI >::ErrType > fmt(Writer< WI > &writer, const FormatOptions &, Optional< T > opt)
Definition formats.hpp:857
static Result< size_t, typename Writer< WI >::ErrType > fmt(Writer< WI > &writer, const FormatOptions &, Result< V, E > res)
Definition formats.hpp:882
static Result< size_t, typename Writer< WI >::ErrType > fmt(Writer< WI > &writer, const FormatOptions &opts, T ptr)
Definition formats.hpp:930
static Result< size_t, typename Writer< WI >::ErrType > fmt(Writer< WI > &writer, FormatOptions opts, const T &iterable)
Definition formats.hpp:488
std::remove_const_t< typename decltype(std::declval< T >().iter())::IterElem > ElemType
Definition formats.hpp:485
static constexpr auto digitTable
Definition formats.hpp:196
static Result< size_t, typename Writer< WI >::ErrType > fmt(Writer< WI > &writer, const FormatOptions &opts, const T &f)
Definition formats.hpp:958
static size_t num_digits(const T &val)
Definition formats.hpp:402
static Result< size_t, typename Writer< WI >::ErrType > fmt(Writer< WI > &writer, const FormatOptions &opts, const T &val)
Definition formats.hpp:219
static Result< size_t, typename Writer< WI >::ErrType > fmt(Writer< WI > &writer, const FormatOptions &opts, T val)
Definition formats.hpp:445
static Result< size_t, typename Writer< WI >::ErrType > fmt(Writer< WI > &writer, const FormatOptions &opts, const char &ch)
Definition formats.hpp:700
static Result< size_t, typename Writer< WI >::ErrType > fmt(Writer< WI > &writer, const FormatOptions &opts, char *ch)
Definition formats.hpp:678
static Result< size_t, typename Writer< WI >::ErrType > fmt(Writer< WI > &writer, const FormatOptions &opts, const char *ch)
Definition formats.hpp:693
Struct to override to specify how a type should be formatted.
Definition format.hpp:45
Formats data with padding into a writer's output stream Uses Formatter<T> under the hood Usually will...
Definition formats.hpp:153
static Result< size_t, typename Writer< WI >::ErrType > write_padded(Writer< WI > &writer, const PaddingOptions &padOpts, const FormatOptions &opts, const T &elem)
Definition formats.hpp:791
Represents parsed padding options for formatting output.
Definition formats.hpp:45
ContentAlignment alignment
Definition formats.hpp:46
A writer that writes data to some sort of stream or buffer Note: the data elements written should be ...
Definition io/writer.hpp:51
typename Impl::ErrType ErrType
Definition io/writer.hpp:53