Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
data_table.hpp
Go to the documentation of this file.
1
5#pragma once
6
7#include "half_int.hpp"
8#include "presentation.hpp"
9
10#include <algorithm>
11#include <array>
12#include <concepts>
13#include <cstdint>
14#include <exception>
15#include <map>
16#include <memory>
17#include <optional>
18#include <ostream>
19#include <span>
20#include <stdexcept>
21#include <string>
22#include <string_view>
23#include <tuple>
24#include <type_traits>
25#include <utility>
26#include <vector>
27
28namespace uni20::presentation
29{
30namespace data_table_detail
31{
32template <typename T> struct optional_traits
33{
34 static constexpr bool optional = false;
35 using value_type = T;
36};
37template <typename T> struct optional_traits<std::optional<T>>
38{
39 static constexpr bool optional = true;
40 using value_type = T;
41};
42template <typename T> using value_type = typename optional_traits<T>::value_type;
43template <typename T> inline constexpr bool half_integer = false;
44template <std::signed_integral T> inline constexpr bool half_integer<basic_half_int<T>> = true;
45
46// Use integer storage types, including int8_t, without treating character text as numbers.
47template <typename T>
48concept integer =
49 std::same_as<T, signed char> || std::same_as<T, unsigned char> || std::same_as<T, short> ||
50 std::same_as<T, unsigned short> || std::same_as<T, int> || std::same_as<T, unsigned int> || std::same_as<T, long> ||
51 std::same_as<T, unsigned long> || std::same_as<T, long long> || std::same_as<T, unsigned long long>;
52
53template <typename T> inline constexpr bool runtime_real = false;
54#if UNI20_HAS_MPREAL
55template <> inline constexpr bool runtime_real<mpreal> = true;
56#endif
57
58// Admit only scalars with a matching formatting, schema and conversion contract.
59template <typename T>
60concept supported_real = std::same_as<T, float> || std::same_as<T, double> || std::same_as<T, long double>
61#if UNI20_HAS_FLOAT128 && UNI20_FLOAT128_PROVIDER_MPLAPACK && defined(MPLAPACK_BINARY128_MODE) && \
62 (MPLAPACK_BINARY128_MODE == MPLAPACK_BINARY128_MODE_FLOAT128)
63 || std::same_as<T, uni20::float128>
64#endif
65 || runtime_real<T>;
66
67template <typename To, typename From> consteval bool accepts()
68{
69 using F = std::remove_cvref_t<From>;
71 {
72 if constexpr (std::same_as<F, std::nullopt_t>)
73 return true;
74 else if constexpr (optional_traits<F>::optional)
75 return accepts<value_type<To>, decltype(*std::declval<From>())>();
76 else
77 return accepts<value_type<To>, From>();
78 }
79 else if constexpr (std::same_as<To, std::string>)
80 return std::constructible_from<std::string, From> && !std::same_as<F, std::nullptr_t>;
81 else if constexpr (std::same_as<To, bool>)
82 return std::same_as<F, bool>;
83 else if constexpr (integer<To>)
84 return integer<F>;
85 else if constexpr (half_integer<To>)
86 return half_integer<F> || integer<F>;
87 else if constexpr (Real<To>)
88 return (Real<F> || integer<F>) && std::constructible_from<To, From>;
89 else
90 return false;
91}
92
93template <typename To, typename From>
94 requires(accepts<To, From>())
95To convert(From&& input)
96{
97 using F = std::remove_cvref_t<From>;
99 {
100 if constexpr (std::same_as<F, std::nullopt_t>)
101 return std::nullopt;
102 else if constexpr (optional_traits<F>::optional)
103 {
104 if (!input) return std::nullopt;
105 return To{convert<value_type<To>>(*std::forward<From>(input))};
106 }
107 else
108 return To{convert<value_type<To>>(std::forward<From>(input))};
109 }
110 else if constexpr (integer<To>)
111 {
112 if (!std::in_range<To>(input)) throw std::overflow_error("data table integer conversion out of range");
113 return static_cast<To>(input);
114 }
115 else if constexpr (half_integer<To>)
116 {
117 if constexpr (half_integer<F>)
118 return To(input);
119 else
120 {
121 using Storage = typename To::value_type;
122 // Check in the source type's range before any narrowing or doubling.
123 if (std::cmp_less(input, uni20::numeric_limits<Storage>::min() / 2) ||
124 std::cmp_greater(input, uni20::numeric_limits<Storage>::max() / 2))
125 throw std::overflow_error("data table half-integer conversion out of range");
126 return To(static_cast<Storage>(input));
127 }
128 }
129 else if constexpr (std::same_as<To, std::string>)
130 {
131 if constexpr (std::is_pointer_v<F>)
132 if (input == nullptr) throw std::invalid_argument("null data table string");
133 return std::string(std::forward<From>(input));
134 }
135 else if constexpr (runtime_real<F>)
136 {
137 if (!input.initialized()) throw std::invalid_argument("unset data table value; use optional for missing data");
138 return static_cast<To>(std::forward<From>(input));
139 }
140 else
141 return static_cast<To>(std::forward<From>(input));
142}
143
144void validate_identifier(std::string_view identifier);
145void validate_numeric_precision(real_format_notation notation, int digits);
146void write_field(std::ostream& out, std::string_view field, char delimiter, bool quote_empty = false);
147void check_output(std::ostream& out);
148
149// Only C-library scalar providers need a thread-local C locale. Never change the process locale.
151 public:
156
157 private:
158 void* previous_;
159};
160
161template <Real T> std::string real_text(T value, scalar_format_options options)
162{
163 if constexpr (runtime_real<T>)
164 {
166 return uni20::format_real(value, options);
167 }
168 else
169#if UNI20_HAS_FLOAT128 && UNI20_FLOAT128_PROVIDER_MPLAPACK && defined(MPLAPACK_BINARY128_MODE) && \
170 (MPLAPACK_BINARY128_MODE == MPLAPACK_BINARY128_MODE_FLOAT128)
171 if constexpr (std::same_as<T, uni20::float128>)
172 {
174 return uni20::format_real(value, options);
175 }
176 else
177#endif
178 {
179 static_assert(std::floating_point<T>, "data table export needs a lossless formatter for this scalar provider");
180 return uni20::format_real(value, options);
181 }
182}
183} // namespace data_table_detail
184
188template <typename T>
190 std::same_as<T, std::remove_cvref_t<T>> &&
193 std::same_as<data_table_detail::value_type<T>, bool> ||
194 data_table_detail::half_integer<data_table_detail::value_type<T>> || std::same_as<T, std::string>);
195
204
206template <DataTableValue T> class data_column {
207 public:
208 using value_type = T;
209 explicit data_column(std::string identifier) : identifier_(std::move(identifier)), label_(identifier_)
210 {
212 if constexpr (std::same_as<T, std::string> || std::same_as<data_table_detail::value_type<T>, bool>)
214 else
216 }
217
218 data_column& label(std::string text)
219 {
220 label_ = std::move(text);
221 return *this;
222 }
223 data_column& unit(std::string text)
224 {
225 unit_ = std::move(text);
226 return *this;
227 }
229 {
230 description_ = std::move(text);
231 return *this;
232 }
234 {
235 display_.alignment = value;
236 return *this;
237 }
239 {
240 display_.missing = std::move(text);
241 return *this;
242 }
243
245 data_column& fixed(int digits)
247 {
248 return this->set_numeric(real_format_notation::fixed, digits);
249 }
253 {
254 return this->set_numeric(real_format_notation::scientific, digits);
255 }
257 data_column& general(int digits = -1)
258 requires Real<data_table_detail::value_type<T>>
259 {
260 return this->set_numeric(real_format_notation::general, digits);
261 }
267 requires Real<data_table_detail::value_type<T>>
268 {
269 this->set_numeric(real_format_notation::general, -1);
270 display_.numeric.normalize_negative_zero = false;
271 return *this;
272 }
275 data_column& fractional(bool enabled = true)
276 requires(data_table_detail::half_integer<data_table_detail::value_type<T>> ||
277 data_table_detail::runtime_real<data_table_detail::value_type<T>>)
278 {
279 display_.fractions = enabled;
280 return *this;
281 }
282
283 [[nodiscard]] std::string const& identifier() const noexcept { return identifier_; }
284 [[nodiscard]] std::string const& label() const noexcept { return label_; }
285 [[nodiscard]] std::string const& unit() const noexcept { return unit_; }
286 [[nodiscard]] std::string const& description() const noexcept { return description_; }
287 [[nodiscard]] data_column_display const& display() const noexcept { return display_; }
288
289 private:
290 data_column& set_numeric(real_format_notation notation, int digits)
291 {
293 display_.numeric.notation = notation;
294 display_.numeric.precision = digits;
295 display_.numeric.normalize_negative_zero = true;
296 if constexpr (data_table_detail::runtime_real<data_table_detail::value_type<T>>) display_.fractions = false;
297 return *this;
298 }
299 std::string identifier_;
300 std::string label_;
301 std::string unit_;
302 std::string description_;
303 data_column_display display_;
304};
305
308{
309 std::vector<std::string> columns = {};
310 std::map<std::string, data_column_display> display = {};
311};
312
314using table_metadata = std::map<std::string, std::string>;
315
317enum class retention
318{
319 all,
320 none
321};
322
329
331enum class sink_replay
332{
333 all,
335};
341
344{
345 std::size_t first_row = 0;
346};
347
350{
351 std::size_t value;
352 bool operator==(data_sink_id const&) const = default;
353};
355{
356 active,
357 closed,
358 failed
359};
361{
362 begin,
363 row,
364 finish
365};
366
375
378{
379 std::vector<data_sink_failure> failures = {};
380};
381
383class data_delivery_error : public std::runtime_error {
384 public:
386 : std::runtime_error("required data table output failed"), report_(std::move(report))
387 {}
388 [[nodiscard]] data_delivery_report const& report() const noexcept { return report_; }
389
390 private:
391 data_delivery_report report_;
392};
393
400
404template <DataTableValue... Ts> class data_table {
405 public:
406 static_assert(sizeof...(Ts) > 0, "data tables require at least one column");
407 using row_type = std::tuple<Ts...>;
408 using schema_type = std::tuple<data_column<Ts>...>;
409
411 : data_table(std::move(title), data_table_options{}, std::move(columns)...)
412 {}
414 : title_(std::move(title)), columns_(std::move(columns)...), options_(std::move(options))
415 {
416 std::apply(
417 [](auto const&... column) {
418 std::array names{std::string_view(column.identifier())...};
419 for (std::size_t i = 0; i < names.size(); ++i)
420 for (std::size_t j = 0; j < i; ++j)
421 if (names[i] == names[j]) throw std::invalid_argument("duplicate data table column identifier");
422 },
423 columns_);
424 }
425 data_table(data_table const&) = delete;
426 data_table& operator=(data_table const&) = delete;
427 data_table(data_table&&) = default;
429
433 template <typename... Us>
434 requires(sizeof...(Ts) == sizeof...(Us) && (data_table_detail::accepts<Ts, Us>() && ...))
436 {
437 mutation_guard guard(busy_);
438 if (finished_) throw std::logic_error("cannot append to a finished data table");
439 auto row = this->make_row(std::forward<Us>(values)...);
440 auto report = this->new_report();
441 if (options_.retain == retention::all) rows_.push_back(std::move(row));
442 ++size_;
443 auto const& accepted = options_.retain == retention::all ? rows_.back() : row;
444 for (auto& sink : sinks_)
445 this->attempt(sink, data_sink_operation::row, report, [&] { sink.output->row(columns_, accepted); });
446 return this->checked_report(std::move(report));
447 }
448
450 template <typename... Us>
451 requires(sizeof...(Ts) == sizeof...(Us) && (data_table_detail::accepts<Ts, Us>() && ...))
452 [[nodiscard]] static row_type make_row(Us&&... values)
453 {
454 return row_type{data_table_detail::convert<Ts>(std::forward<Us>(values))...};
455 }
456
460 template <typename Sink> data_attachment attach(Sink&& output, data_sink_options options = {})
461 {
462 mutation_guard guard(busy_);
463 if (options.replay == sink_replay::all && options_.retain == retention::none && size_ != 0)
464 throw std::logic_error("data table history unavailable; request future_only attachment explicitly");
465 auto owned = std::make_unique<sink_model<std::remove_cvref_t<Sink>>>(std::forward<Sink>(output));
467 report.failures.reserve(1);
468 data_sink_id id{sinks_.size()};
469 sinks_.push_back({id, options.required, data_sink_state::active, std::move(owned)});
470 auto& sink = sinks_.back();
471 this->attempt(sink, data_sink_operation::begin, report, [&] {
472 sink.output->begin(title_, columns_, options_.metadata, {options.replay == sink_replay::all ? 0 : size_});
473 });
474 if (options.replay == sink_replay::all)
475 for (auto const& row : rows_)
476 {
477 if (sink.state != data_sink_state::active) break;
478 this->attempt(sink, data_sink_operation::row, report, [&] { sink.output->row(columns_, row); });
479 }
480 if (finished_) this->close(sink, *summary_, report);
481 return {id, this->checked_report(std::move(report))};
482 }
483
486 {
487 mutation_guard guard(busy_);
488 auto report = this->new_report();
489 this->close(this->find_sink(id), summary, report);
490 return this->checked_report(std::move(report));
491 }
492
497 {
498 mutation_guard guard(busy_);
499 if (finished_)
500 {
501 if (!summary.empty() && summary != *summary_) throw std::logic_error("data table summary already finalized");
502 return this->checked_report(final_report_);
503 }
504 auto report = this->new_report();
505 summary_ = std::move(summary);
506 finished_ = true;
507 for (auto& sink : sinks_)
508 this->close(sink, *summary_, report);
509 final_report_ = std::move(report);
510 return this->checked_report(final_report_);
511 }
512
513 [[nodiscard]] std::string const& title() const noexcept { return title_; }
514 [[nodiscard]] schema_type const& columns() const noexcept { return columns_; }
515 [[nodiscard]] table_metadata const& metadata() const noexcept { return options_.metadata; }
516 [[nodiscard]] std::optional<table_metadata> const& summary() const noexcept { return summary_; }
517 [[nodiscard]] bool finished() const noexcept { return finished_; }
518 [[nodiscard]] retention retention_policy() const noexcept { return options_.retain; }
519 [[nodiscard]] std::size_t size() const noexcept { return size_; }
520 [[nodiscard]] std::size_t retained_size() const noexcept { return rows_.size(); }
522 [[nodiscard]] std::span<row_type const> rows() const
523 {
524 if (options_.retain == retention::none) throw std::logic_error("data table does not retain history");
525 return rows_;
526 }
527 [[nodiscard]] data_sink_state sink_state(data_sink_id id) const { return this->find_sink(id).state; }
528
529 private:
530 struct mutation_guard
531 {
532 bool& busy;
533 explicit mutation_guard(bool& flag) : busy(flag)
534 {
535 if (busy) throw std::logic_error("data table mutation is not reentrant");
536 busy = true;
537 }
538 ~mutation_guard() { busy = false; }
539 };
540 struct sink_interface
541 {
542 virtual ~sink_interface() = default;
543 virtual void begin(std::string const&, schema_type const&, table_metadata const&, data_sink_start) = 0;
544 virtual void row(schema_type const&, row_type const&) = 0;
545 virtual void finish(table_metadata const&) = 0;
546 };
547 template <typename Sink> struct sink_model final : sink_interface
548 {
549 Sink output;
550 explicit sink_model(Sink value) : output(std::move(value)) {}
551 void begin(std::string const& title, schema_type const& schema, table_metadata const& metadata,
552 data_sink_start start) override
553 {
554 output.begin(title, schema, metadata, start);
555 }
556 void row(schema_type const& schema, row_type const& row) override { output.row(schema, row); }
557 void finish(table_metadata const& summary) override { output.finish(summary); }
558 };
559 struct sink_record
560 {
561 data_sink_id id;
562 bool required;
563 data_sink_state state;
564 std::unique_ptr<sink_interface> output;
565 };
566 data_delivery_report new_report() const
567 {
568 data_delivery_report report;
569 report.failures.reserve(sinks_.size());
570 return report;
571 }
572 static data_delivery_report checked_report(data_delivery_report report)
573 {
574 if (std::any_of(report.failures.begin(), report.failures.end(), [](auto const& f) { return f.required; }))
575 throw data_delivery_error(std::move(report));
576 return report;
577 }
578 template <typename Function>
579 void attempt(sink_record& sink, data_sink_operation operation, data_delivery_report& report, Function&& function)
580 {
581 if (sink.state != data_sink_state::active) return;
582 try
583 {
584 function();
585 }
586 catch (...)
587 {
589 report.failures.push_back({sink.id, sink.required, operation, std::current_exception()});
590 sink.output.reset();
591 }
592 }
593 void close(sink_record& sink, table_metadata const& summary, data_delivery_report& report)
594 {
595 this->attempt(sink, data_sink_operation::finish, report, [&] { sink.output->finish(summary); });
596 if (sink.state == data_sink_state::active)
597 {
599 sink.output.reset();
600 }
601 }
602 sink_record const& find_sink(data_sink_id id) const
603 {
604 if (id.value >= sinks_.size()) throw std::invalid_argument("unknown data table sink");
605 return sinks_[id.value];
606 }
607 sink_record& find_sink(data_sink_id id) { return const_cast<sink_record&>(std::as_const(*this).find_sink(id)); }
608
609 std::string title_;
610 schema_type columns_;
611 data_table_options options_;
612 std::vector<row_type> rows_;
613 std::vector<sink_record> sinks_;
614 std::size_t size_ = 0;
615 bool busy_ = false;
616 bool finished_ = false;
617 std::optional<table_metadata> summary_;
618 data_delivery_report final_report_;
619};
620
622template <DataTableValue... Ts> [[nodiscard]] auto make_data_table(std::string title, data_column<Ts>... columns)
623{
624 return data_table<Ts...>(std::move(title), std::move(columns)...);
625}
626
628template <DataTableValue... Ts>
629[[nodiscard]] auto make_data_table(std::string title, data_table_options options, data_column<Ts>... columns)
630{
631 return data_table<Ts...>(std::move(title), std::move(options), std::move(columns)...);
632}
633
634namespace data_table_detail
635{
636template <typename T> std::string cell_text(T const& value, data_column_display const& display, bool machine)
637{
638 if constexpr (optional_traits<T>::optional)
639 {
640 if (!value) return machine ? "" : display.missing;
641 return cell_text(*value, display, machine);
642 }
643 else if constexpr (std::same_as<T, std::string>)
644 return value;
645 else if constexpr (std::same_as<T, bool>)
646 return value ? "true" : "false";
647 else if constexpr (half_integer<T>)
648 return !machine && display.fractions ? uni20::to_string_fraction(value) : uni20::to_string(value);
649 else if constexpr (integer<T>)
650 return fmt::format("{}", value);
651 else
652 {
653 scalar_format_options options = machine ? scalar_format_options{} : display.numeric;
654 if constexpr (runtime_real<T>)
655 {
656 // Exact values have no max_digits10: the caller must select fractions or
657 // a decimal digit count. Machine output uses this choice for exact values
658 // only; approximate values keep their own round-trip precision.
659 if (value.is_exact()) options = display.numeric;
660 options.exact_fractions = display.fractions;
661 }
662 if (machine) options.normalize_negative_zero = false;
663 return real_text(value, options);
664 }
665}
666
667template <typename Schema, typename Row, typename Function>
668void visit_cells(Schema const& schema, Row const& row, Function&& function)
669{
670 [&]<std::size_t... I>(std::index_sequence<I...>) {
671 (function(std::get<I>(schema), std::get<I>(row)), ...);
672 }(std::make_index_sequence<std::tuple_size_v<Schema>>{});
673}
675{
676 std::vector<std::size_t> indices;
677 std::vector<data_column_display> display;
678};
679
680template <typename Schema, typename Function>
681void visit_column(Schema const& schema, std::size_t index, Function&& function)
682{
683 [&]<std::size_t... I>(std::index_sequence<I...>) {
684 ((I == index ? (void)function(std::get<I>(schema)) : (void)0), ...);
685 }(std::make_index_sequence<std::tuple_size_v<Schema>>{});
686}
687
688template <typename Schema> resolved_projection resolve_projection(Schema const& schema, table_projection const& options)
689{
690 resolved_projection result;
691 std::vector<std::string> names;
692 std::apply([&](auto const&... column) { (names.push_back(column.identifier()), ...); }, schema);
693 auto add = [&](std::string const& name) {
694 auto found = std::find(names.begin(), names.end(), name);
695 if (found == names.end()) throw std::invalid_argument("unknown data table output column: " + name);
696 auto index = static_cast<std::size_t>(found - names.begin());
697 if (std::find(result.indices.begin(), result.indices.end(), index) != result.indices.end())
698 throw std::invalid_argument("duplicate data table output column: " + name);
699 result.indices.push_back(index);
700 visit_column(schema, index, [&](auto const& c) { result.display.push_back(c.display()); });
701 if (auto override = options.display.find(name); override != options.display.end())
702 result.display.back() = override->second;
703 };
704 for (auto const& name : options.columns.empty() ? names : options.columns)
705 add(name);
706 for (auto const& [name, format] : options.display)
707 {
708 auto found = std::find(names.begin(), names.end(), name);
709 if (found == names.end() ||
710 std::find(result.indices.begin(), result.indices.end(), found - names.begin()) == result.indices.end())
711 throw std::invalid_argument("display override requires a selected data table column: " + name);
712 auto const& numeric = format.numeric;
713 validate_numeric_precision(numeric.notation, numeric.precision);
714 }
715 return result;
716}
717
718template <typename Schema, typename Row, typename Function>
719void visit_selected(Schema const& schema, Row const& row, resolved_projection const& selection, Function&& function)
720{
721 for (std::size_t p = 0; p < selection.indices.size(); ++p)
722 [&]<std::size_t... I>(std::index_sequence<I...>) {
723 ((I == selection.indices[p] ? (void)function(std::get<I>(schema), std::get<I>(row), selection.display[p])
724 : (void)0),
725 ...);
726 }(std::make_index_sequence<std::tuple_size_v<Schema>>{});
727}
728} // namespace data_table_detail
729
731template <DataTableValue... Ts>
732[[nodiscard]] report_table to_report_table(data_table<Ts...> const& table, table_projection const& projection = {})
733{
734 auto rows = table.rows();
735 report_table result(table.title());
736 auto selection = data_table_detail::resolve_projection(table.columns(), projection);
737 for (std::size_t p = 0; p < selection.indices.size(); ++p)
738 data_table_detail::visit_column(table.columns(), selection.indices[p], [&](auto const& column) {
739 result.column(column.label(), selection.display[p].alignment);
740 });
741 for (auto const& row : rows)
742 {
743 std::vector<std::string> cells;
744 cells.reserve(sizeof...(Ts));
745 data_table_detail::visit_selected(table.columns(), row, selection,
746 [&](auto const&, auto const& value, auto const& display) {
747 cells.push_back(data_table_detail::cell_text(value, display, false));
748 });
749 result.row(std::move(cells));
750 }
751 return result;
752}
753
756{
758 display
759};
760
767
768namespace data_table_detail
769{
770template <typename T>
771std::string export_text(T const& value, data_column_display const& display, delimited_options const& options)
772{
773 if constexpr (optional_traits<T>::optional)
774 {
775 if (!value) return "";
776 return export_text(*value, display, options);
777 }
778 else
780}
781
782template <typename Schema>
783void write_delimited_header(std::ostream& out, Schema const& schema, resolved_projection const& selection,
784 char delimiter)
785{
786 check_output(out);
787 bool first = true;
788 for (auto index : selection.indices)
789 visit_column(schema, index, [&](auto const& column) {
790 if (!std::exchange(first, false)) out.put(delimiter);
791 write_field(out, column.identifier(), delimiter);
792 });
793 out.put('\n');
794 check_output(out);
795}
796
797template <typename Schema, typename Row>
798void write_delimited_row(std::ostream& out, Schema const& schema, Row const& row, resolved_projection const& selection,
799 char delimiter, delimited_options const& options)
800{
801 bool first = true;
802 visit_selected(schema, row, selection, [&](auto const&, auto const& value, auto const& display) {
803 if (!std::exchange(first, false)) out.put(delimiter);
804 using T = std::remove_cvref_t<decltype(value)>;
805 auto text = export_text(value, display, options);
806 write_field(out, text, delimiter, std::same_as<T, std::string> || selection.indices.size() == 1);
807 });
808 out.put('\n');
809 check_output(out);
810}
811
812template <DataTableValue... Ts>
813void write_delimited(std::ostream& out, data_table<Ts...> const& table, char delimiter,
814 delimited_options const& options)
815{
816 auto rows = table.rows();
817 auto selection = resolve_projection(table.columns(), options.projection);
818 write_delimited_header(out, table.columns(), selection, delimiter);
819 for (auto const& row : rows)
820 write_delimited_row(out, table.columns(), row, selection, delimiter, options);
821}
822} // namespace data_table_detail
823
828template <DataTableValue... Ts>
829void write_csv(std::ostream& out, data_table<Ts...> const& table, delimited_options options = {})
830{
831 data_table_detail::write_delimited(out, table, ',', options);
832}
833
836template <DataTableValue... Ts>
837void write_tsv(std::ostream& out, data_table<Ts...> const& table, delimited_options options = {})
838{
839 data_table_detail::write_delimited(out, table, '\t', options);
840}
841
842} // namespace uni20::presentation
Typed column schema with owned metadata and deferred display formatting.
Definition data_table.hpp:206
data_column(std::string identifier)
Definition data_table.hpp:209
data_column & label(std::string text)
Definition data_table.hpp:218
data_column & fractional(bool enabled=true)
Select exact fractions for half-integers or exact mpreal values.
Definition data_table.hpp:275
data_column & round_trip()
Recover finite real values from their text, including the sign of zero.
Definition data_table.hpp:266
data_column & fixed(int digits)
Use fractional decimal digits; -1 selects native max_digits10 or approximate MPFR round-trip digits.
Definition data_table.hpp:245
T value_type
Definition data_table.hpp:208
data_column & missing(std::string text)
Definition data_table.hpp:238
data_column & description(std::string text)
Definition data_table.hpp:228
std::string const & label() const noexcept
Definition data_table.hpp:284
std::string const & unit() const noexcept
Definition data_table.hpp:285
data_column & alignment(table_alignment value)
Definition data_table.hpp:233
data_column_display const & display() const noexcept
Definition data_table.hpp:287
std::string const & description() const noexcept
Definition data_table.hpp:286
std::string const & identifier() const noexcept
Definition data_table.hpp:283
data_column & general(int digits=-1)
Use significant digits; -1 selects native max_digits10 or approximate MPFR round-trip digits.
Definition data_table.hpp:257
data_column & scientific(int digits)
Use fractional mantissa digits; -1 selects native max_digits10 or approximate MPFR round-trip digits.
Definition data_table.hpp:251
data_column & unit(std::string text)
Definition data_table.hpp:223
Required output failed after all eligible sinks were attempted; retained rows are not rolled back.
Definition data_table.hpp:383
data_delivery_report const & report() const noexcept
Definition data_table.hpp:388
data_delivery_error(data_delivery_report report)
Definition data_table.hpp:385
classic_numeric_locale(classic_numeric_locale const &)=delete
classic_numeric_locale & operator=(classic_numeric_locale const &)=delete
Typed result table with synchronous sinks and optional retained history.
Definition data_table.hpp:404
retention retention_policy() const noexcept
Definition data_table.hpp:518
data_delivery_report finish_sink(data_sink_id id, table_metadata const &summary={})
Finalize and unsubscribe one sink without ending the table or discarding rows.
Definition data_table.hpp:485
data_table & operator=(data_table const &)=delete
data_table(std::string title, data_column< Ts >... columns)
Definition data_table.hpp:410
bool finished() const noexcept
Definition data_table.hpp:517
static row_type make_row(Us &&... values)
Construct a checked, owning row independently of storage or output.
Definition data_table.hpp:452
table_metadata const & metadata() const noexcept
Definition data_table.hpp:515
std::size_t retained_size() const noexcept
Definition data_table.hpp:520
std::size_t size() const noexcept
Definition data_table.hpp:519
std::string const & title() const noexcept
Definition data_table.hpp:513
std::span< row_type const > rows() const
Require complete retained history before inspecting or exporting rows.
Definition data_table.hpp:522
data_sink_state sink_state(data_sink_id id) const
Definition data_table.hpp:527
data_table & operator=(data_table &&)=delete
data_attachment attach(Sink &&output, data_sink_options options={})
Own a sink, begin its output and replay history before subscribing to future rows.
Definition data_table.hpp:460
data_table(data_table &&)=default
data_delivery_report append(Us &&... values)
Validate once, retain when enabled, then deliver to every active sink in attachment order.
Definition data_table.hpp:435
data_delivery_report finish(table_metadata summary={})
End row insertion and finalize all active outputs, retaining the summary for later replay.
Definition data_table.hpp:496
data_table(std::string title, data_table_options options, data_column< Ts >... columns)
Definition data_table.hpp:413
data_table(data_table const &)=delete
std::optional< table_metadata > const & summary() const noexcept
Definition data_table.hpp:516
schema_type const & columns() const noexcept
Definition data_table.hpp:514
std::tuple< data_column< Ts >... > schema_type
Definition data_table.hpp:408
std::tuple< Ts... > row_type
Definition data_table.hpp:407
A small report table builder for command-line examples and diagnostics.
Definition presentation.hpp:479
Concept for real scalar types (float, double, etc.).
Definition scalar_concepts.hpp:69
Types retained directly by data tables, without numerical narrowing or text conversion.
Definition data_table.hpp:189
locale_t value
Definition data_table.cpp:49
presentation::styled_text text
Definition display.cpp:141
std::string format_real(T value, scalar_format_options const &options={})
Format a Uni20 real scalar.
Definition scalar_io.hpp:306
real_format_notation
Selects how real scalar values are rendered as text.
Definition scalar_io.hpp:29
Defines basic_half_int<T>, a signed integer type with half-integer resolution.
Definition qnum.hpp:403
Definition cli.cpp:6
Definition display.cpp:16
std::function< void(event const &)> sink
Callable that receives display events for final routing and rendering.
Definition display.hpp:126
void validate_numeric_precision(real_format_notation notation, int digits)
Definition data_table.cpp:9
constexpr bool runtime_real
Definition data_table.hpp:53
constexpr bool half_integer
Definition data_table.hpp:43
void visit_selected(Schema const &schema, Row const &row, resolved_projection const &selection, Function &&function)
Definition data_table.hpp:719
void write_delimited(std::ostream &out, data_table< Ts... > const &table, char delimiter, delimited_options const &options)
Definition data_table.hpp:813
consteval bool accepts()
Definition data_table.hpp:67
To convert(From &&input)
Definition data_table.hpp:95
std::string export_text(T const &value, data_column_display const &display, delimited_options const &options)
Definition data_table.hpp:771
void check_output(std::ostream &out)
Definition data_table.cpp:40
resolved_projection resolve_projection(Schema const &schema, table_projection const &options)
Definition data_table.hpp:688
void write_delimited_row(std::ostream &out, Schema const &schema, Row const &row, resolved_projection const &selection, char delimiter, delimited_options const &options)
Definition data_table.hpp:798
void visit_column(Schema const &schema, std::size_t index, Function &&function)
Definition data_table.hpp:681
void validate_identifier(std::string_view identifier)
Definition data_table.cpp:16
std::string cell_text(T const &value, data_column_display const &display, bool machine)
Definition data_table.hpp:636
std::string real_text(T value, scalar_format_options options)
Definition data_table.hpp:161
void visit_cells(Schema const &schema, Row const &row, Function &&function)
Definition data_table.hpp:668
void write_delimited_header(std::ostream &out, Schema const &schema, resolved_projection const &selection, char delimiter)
Definition data_table.hpp:783
typename optional_traits< T >::value_type value_type
Definition data_table.hpp:42
void write_field(std::ostream &out, std::string_view field, char delimiter, bool quote_empty)
Definition data_table.cpp:27
Definition data_table.cpp:8
void write_tsv(std::ostream &out, data_table< Ts... > const &table, delimited_options options={})
Write tab-delimited CSV using CSV quoting, not whitespace-token splitting.
Definition data_table.hpp:837
data_export_precision
Precision for delimited numerical output; display mode deliberately rounds real values.
Definition data_table.hpp:756
table_alignment
Selects table cell alignment in report-style output.
Definition presentation.hpp:89
std::map< std::string, std::string > table_metadata
Application metadata is owned text, independent of numerical rows.
Definition data_table.hpp:314
retention
Whether accepted rows remain available for snapshots and replay.
Definition data_table.hpp:318
data_sink_state
Definition data_table.hpp:355
auto make_data_table(std::string title, data_column< Ts >... columns)
Deduce column types with default retained history.
Definition data_table.hpp:622
void write_csv(std::ostream &out, data_table< Ts... > const &table, delimited_options options={})
Write a CSV snapshot with stable headers, quoted strings and empty missing numeric fields.
Definition data_table.hpp:829
sink_replay
Full replay is the default. Future-only attachment explicitly opts out of history.
Definition data_table.hpp:332
report_table to_report_table(data_table< Ts... > const &table, table_projection const &projection={})
Format a snapshot using column display settings and the existing rich table model.
Definition data_table.hpp:732
data_sink_operation
Definition data_table.hpp:361
auto to_string_fraction(basic_half_int< T > value) -> std::string
Format a half-integer as an integer or n/2.
Definition half_int.hpp:464
int add(int a, int b)
Definition dummy.cpp:6
auto to_string(basic_half_int< T > value) -> std::string
Format a half-integer as an integer or decimal .5.
Definition half_int.hpp:477
Project-level numeric limits customization point.
Definition numeric_limits.hpp:16
Attachment identity and any optional begin/replay/finalization failure.
Definition data_table.hpp:396
data_delivery_report report
Definition data_table.hpp:398
data_sink_id id
Definition data_table.hpp:397
Column display settings. These never change stored values or default export precision.
Definition data_table.hpp:198
std::string missing
Definition data_table.hpp:201
table_alignment alignment
Definition data_table.hpp:200
bool fractions
Definition data_table.hpp:202
scalar_format_options numeric
Definition data_table.hpp:199
Newly encountered failures. Callers using optional sinks must inspect this result.
Definition data_table.hpp:378
std::vector< data_sink_failure > failures
Definition data_table.hpp:379
Original exception and attachment identity for one disabled sink.
Definition data_table.hpp:369
bool required
Definition data_table.hpp:371
data_sink_operation operation
Definition data_table.hpp:372
std::exception_ptr exception
Definition data_table.hpp:373
data_sink_id sink
Definition data_table.hpp:370
A table-local attachment identifier, stable when its table is moved.
Definition data_table.hpp:350
std::size_t value
Definition data_table.hpp:351
bool operator==(data_sink_id const &) const =default
Definition data_table.hpp:337
bool required
Definition data_table.hpp:338
sink_replay replay
Definition data_table.hpp:339
The index of the first row this attachment can emit (zero for full replay).
Definition data_table.hpp:344
std::size_t first_row
Definition data_table.hpp:345
static constexpr bool optional
Definition data_table.hpp:34
std::vector< data_column_display > display
Definition data_table.hpp:677
std::vector< std::size_t > indices
Definition data_table.hpp:676
Construction-time table policy and initial application metadata.
Definition data_table.hpp:325
retention retain
Definition data_table.hpp:326
table_metadata metadata
Definition data_table.hpp:327
Delimited output policy, independent of terminal styles and width.
Definition data_table.hpp:763
table_projection projection
Definition data_table.hpp:765
data_export_precision precision
Definition data_table.hpp:764
Ordered output columns and independent display overrides. Empty columns selects the full schema.
Definition data_table.hpp:308
std::map< std::string, data_column_display > display
Definition data_table.hpp:310
std::vector< std::string > columns
Definition data_table.hpp:309
Formatting options for Uni20 real and complex scalars.
Definition scalar_io.hpp:38
real_format_notation notation
Definition scalar_io.hpp:40
int precision
Definition scalar_io.hpp:39
bool normalize_negative_zero
Definition scalar_io.hpp:41
bool exact_fractions
Exact rationals use fractions unless decimal rounding is explicitly requested.
Definition scalar_io.hpp:44