Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
metadata.hpp
Go to the documentation of this file.
1
5#pragma once
6#include "data_table.hpp"
7#include "half_int.hpp"
8#include <algorithm>
9#include <any>
10#include <charconv>
11#include <concepts>
12#include <cstdint>
13#include <map>
14#include <memory>
15#include <optional>
16#include <stdexcept>
17#include <string>
18#include <string_view>
19#include <system_error>
20#include <tuple>
21#include <typeindex>
22#include <uni20/core/math.hpp>
26#include <uni20/core/types.hpp>
27#include <utility>
28#include <vector>
29
30namespace uni20
31{
32namespace metadata_detail
33{
34namespace dt = presentation::data_table_detail;
35
36template <typename T> T parse(std::string_view text)
37{
38 if constexpr (std::same_as<T, std::string>)
39 return std::string(text);
40 else if constexpr (std::same_as<T, bool>)
41 {
42 if (text == "true" || text == "1") return true;
43 if (text == "false" || text == "0") return false;
44 throw std::invalid_argument("expected true, false, 1 or 0");
45 }
46 else if constexpr (dt::integer<T>)
47 {
48 T value{};
49 auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value);
50 if (error == std::errc::result_out_of_range) throw std::out_of_range("integer out of range");
51 if (text.empty() || error != std::errc{} || end != text.data() + text.size())
52 throw std::invalid_argument("expected a complete integer literal");
53 return value;
54 }
55 else if constexpr (dt::half_integer<T>)
56 return T::parse(text);
57 else
58 return parse_real<T>(text);
59}
60} // namespace metadata_detail
61
67 public:
68 template <presentation::DataTableValue T>
69 metadata_value(T value) : storage_(std::make_shared<model<T>>(metadata_detail::dt::convert<T>(std::move(value))))
70 {}
71 metadata_value(std::string_view text) : metadata_value(std::string(text)) {}
72 metadata_value(char const* text) : metadata_value(copy_text(text)) {}
73
74 [[nodiscard]] std::type_index type() const { return storage_->type(); }
75 [[nodiscard]] bool missing() const { return !storage_->scalar().has_value(); }
76 [[nodiscard]] std::string text(presentation::data_column_display const& format = {}, bool machine = false) const
77 {
78 return storage_->text(format, machine);
79 }
80
82 template <typename T> [[nodiscard]] T const& get() const { return std::any_cast<T const&>(storage_->value()); }
83
87 template <presentation::DataTableValue T> [[nodiscard]] T as() const
88 {
89 namespace dt = metadata_detail::dt;
90 if (auto p = std::any_cast<T>(&storage_->value())) return *p;
92 {
93 if (this->missing()) return std::nullopt;
94 return this->as<typename dt::optional_traits<T>::value_type>();
95 }
96 else
97 {
98 if (this->missing()) throw std::invalid_argument("missing value for a non-optional field");
99 auto const& scalar = storage_->scalar();
100 if (auto p = std::any_cast<T>(&scalar)) return *p;
101 if constexpr (dt::runtime_real<T>)
102 throw std::invalid_argument("conversion to mpreal requires explicit Precision");
103 else
104 {
105 if (auto p = std::any_cast<std::string>(&scalar)) return metadata_detail::parse<T>(*p);
106 std::optional<T> result;
107 auto try_type = [&]<typename U>() {
108 // Only stored types can occur in the any. A configured scalar may
109 // have core conversions without the lossless table I/O contract.
110 if constexpr (presentation::DataTableValue<U> && dt::accepts<T, U>())
111 if (auto p = std::any_cast<U>(&scalar))
112 {
113 // T belongs to the enclosing template, U to this generic lambda.
114 // Discard non-real destinations before inspecting their limits.
115 if constexpr (Real<T>)
116 {
117 if constexpr (dt::runtime_real<U>)
118 {
119#if UNI20_HAS_MPREAL
120 auto bound =
122 if (uni20::isfinite(*p) && (*p > bound || *p < -bound))
123 throw std::out_of_range("real conversion overflow");
124#endif
125 }
126 else if constexpr (Real<U>)
127 {
131 if (uni20::isfinite(*p) && (*p > static_cast<U>(uni20::numeric_limits<T>::max()) ||
132 *p < -static_cast<U>(uni20::numeric_limits<T>::max())))
133 throw std::out_of_range("real conversion overflow");
134 }
135 }
136 auto converted = dt::convert<T>(*p);
137 result = std::move(converted);
138 }
139 };
140 [&]<typename... U>(std::tuple<U...>*) {
141 (try_type.template operator()<U>(), ...);
142 }(static_cast<
143 std::tuple<signed char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long,
144 long long, unsigned long long, float, double, long double, basic_half_int<std::intmax_t>>*>(
145 nullptr));
146#if UNI20_HAS_FLOAT128
147 try_type.template operator()<uni20::float128>();
148#endif
149#if UNI20_HAS_MPREAL
150 try_type.template operator()<mpreal>();
151#endif
152 if (!result) throw std::invalid_argument("incompatible metadata scalar types");
153 return std::move(*result);
154 }
155 }
156 }
157
158#if UNI20_HAS_MPREAL
163 template <presentation::DataTableValue T>
164 requires metadata_detail::dt::runtime_real<metadata_detail::dt::value_type<T>>
165 [[nodiscard]] T as(Precision precision) const
166 {
167 namespace dt = metadata_detail::dt;
169 {
170 if (this->missing()) return std::nullopt;
171 return this->as<mpreal>(precision);
172 }
173 else
174 {
175 if (this->missing()) throw std::invalid_argument("missing value for a non-optional field");
176 auto const& scalar = storage_->scalar();
177 if (auto p = std::any_cast<mpreal>(&scalar)) return p->at(precision);
178 if (auto p = std::any_cast<std::string>(&scalar)) return parse_real<mpreal>(*p, precision);
179 std::optional<mpreal> result;
180 auto try_type = [&]<typename U>() {
181 if constexpr (presentation::DataTableValue<U>)
182 if (auto p = std::any_cast<U>(&scalar)) result.emplace(*p, precision);
183 };
184 [&]<typename... U>(std::tuple<U...>*) {
185 (try_type.template operator()<U>(), ...);
186 }(static_cast<std::tuple<signed char, unsigned char, short, unsigned short, int, unsigned int, long,
187 unsigned long, long long, unsigned long long, float, double, long double>*>(nullptr));
188#if UNI20_HAS_FLOAT128
189 try_type.template operator()<uni20::float128>();
190#endif
191 if (!result) throw std::invalid_argument("incompatible metadata scalar types");
192 return std::move(*result);
193 }
194 }
195#endif
196
197 private:
198 struct interface
199 {
200 virtual ~interface() = default;
201 virtual std::type_index type() const = 0;
202 virtual std::any const& value() const = 0;
203 virtual std::any const& scalar() const = 0;
204 virtual std::string text(presentation::data_column_display const&, bool) const = 0;
205 };
206 template <typename T> struct model final : interface
207 {
208 explicit model(T value) : value_(std::move(value))
209 {
210 auto const& v = std::any_cast<T const&>(value_);
211 if constexpr (metadata_detail::dt::optional_traits<T>::optional)
212 {
213 if (v) this->set_scalar(*v);
214 }
215 else
216 this->set_scalar(v);
217 }
218 std::type_index type() const override { return typeid(T); }
219 std::any const& value() const override { return value_; }
220 std::any const& scalar() const override { return scalar_; }
221 std::string text(presentation::data_column_display const& format, bool machine) const override
222 {
223 return metadata_detail::dt::cell_text(std::any_cast<T const&>(value_), format, machine);
224 }
225 std::any value_;
226 std::any scalar_;
227 template <typename V> void set_scalar(V const& value)
228 {
229 if constexpr (metadata_detail::dt::half_integer<V>)
230 scalar_ = basic_half_int<std::intmax_t>(value);
231 else
232 scalar_ = value;
233 }
234 };
235 static std::string copy_text(char const* text)
236 {
237 if (!text) throw std::invalid_argument("null metadata text");
238 return text;
239 }
240 std::shared_ptr<interface const> storage_;
241};
242
252
253[[nodiscard]] inline std::string_view to_string(configuration_source source)
254{
255 switch (source)
256 {
258 return "explicit input";
260 return "command line";
262 return "option file";
264 return "attribute";
266 return "environment";
268 return "default";
269 }
270 throw std::invalid_argument("invalid configuration source");
271}
272
280
282{
283 std::string label = {};
284 std::string unit = {};
285 std::string description = {};
287 bool detail = false;
288 std::optional<value_origin> origin = std::nullopt;
289};
290
297
299{
300 std::string id;
301 std::string label;
302 std::vector<metadata_field> fields = {};
303};
304
307 public:
308 void group(std::string id, std::string label = {})
309 {
310 metadata_detail::dt::validate_identifier(id);
311 if (std::ranges::any_of(groups_, [&](auto const& g) { return g.id == id; }))
312 throw std::invalid_argument("duplicate metadata group: " + id);
313 if (label.empty()) label = id;
314 groups_.push_back({std::move(id), std::move(label)});
315 }
316 void add(std::string const& group, std::string id, metadata_value value, metadata_field_options options = {})
317 {
318 metadata_detail::dt::validate_identifier(id);
319 if (this->find(id)) throw std::invalid_argument("duplicate metadata field: " + id);
320 auto found = std::ranges::find(groups_, group, &metadata_group::id);
321 if (found == groups_.end()) throw std::invalid_argument("unknown metadata group: " + group);
322 if (options.label.empty()) options.label = id;
323 found->fields.push_back({std::move(id), std::move(value), std::move(options)});
324 }
325 void replace(std::string const& id, metadata_value value)
326 {
327 for (auto& group : groups_)
328 for (auto& field : group.fields)
329 if (field.id == id)
330 {
331 if (field.value.type() != value.type()) throw std::invalid_argument("metadata replacement changes type");
332 field.value = std::move(value);
333 return;
334 }
335 throw std::invalid_argument("unknown metadata field: " + id);
336 }
337 [[nodiscard]] metadata_field const* find(std::string_view id) const
338 {
339 for (auto const& group : groups_)
340 for (auto const& field : group.fields)
341 if (field.id == id) return &field;
342 return nullptr;
343 }
344 [[nodiscard]] std::vector<metadata_group> const& groups() const { return groups_; }
346 void append(metadata_document const& other)
347 {
348 auto combined = *this;
349 for (auto const& group : other.groups())
350 {
351 combined.group(group.id, group.label);
352 for (auto const& field : group.fields)
353 combined.add(group.id, field.id, field.value, field.options);
354 }
355 *this = std::move(combined);
356 }
357
359 [[nodiscard]] presentation::table_metadata strings(std::map<std::string, std::string> const& keys = {}) const
360 {
361 for (auto const& [id, key] : keys)
362 if (!this->find(id)) throw std::invalid_argument("unknown metadata export field: " + id);
364 for (auto const& group : groups_)
365 for (auto const& field : group.fields)
366 {
367 auto key = keys.contains(field.id) ? keys.at(field.id) : field.id;
368 if (!result.emplace(key, field.value.text(field.options.display, true)).second)
369 throw std::invalid_argument("duplicate metadata export key: " + key);
370 }
371 return result;
372 }
373
374 private:
375 std::vector<metadata_group> groups_;
376};
377} // namespace uni20
Exact arithmetic or an explicit binary working precision.
Definition core/precision.hpp:13
static constexpr Precision bits(bit_count_type count)
Select a positive binary significand size.
Definition core/precision.hpp:21
Signed value type that stores multiples of one half exactly.
Definition half_int.hpp:34
Ordered, uniquely identified metadata. Copies are independent snapshots.
Definition metadata.hpp:306
void replace(std::string const &id, metadata_value value)
Definition metadata.hpp:325
std::vector< metadata_group > const & groups() const
Definition metadata.hpp:344
void append(metadata_document const &other)
Append disjoint groups from an owned snapshot, rejecting all collisions before mutation.
Definition metadata.hpp:346
metadata_field const * find(std::string_view id) const
Definition metadata.hpp:337
presentation::table_metadata strings(std::map< std::string, std::string > const &keys={}) const
Explicitly project to the table format's flat string metadata, checking export-key collisions.
Definition metadata.hpp:359
void group(std::string id, std::string label={})
Definition metadata.hpp:308
void add(std::string const &group, std::string id, metadata_value value, metadata_field_options options={})
Definition metadata.hpp:316
An immutable owned value that retains its native scalar and optional type.
Definition metadata.hpp:66
std::string text(presentation::data_column_display const &format={}, bool machine=false) const
Definition metadata.hpp:76
metadata_value(std::string_view text)
Definition metadata.hpp:71
metadata_value(T value)
Definition metadata.hpp:69
std::type_index type() const
Definition metadata.hpp:74
bool missing() const
Definition metadata.hpp:75
T as() const
Convert without a double intermediate; textual numbers use the destination's parser.
Definition metadata.hpp:87
T const & get() const
Access the exact declared type; a mismatch throws std::bad_any_cast.
Definition metadata.hpp:82
metadata_value(char const *text)
Definition metadata.hpp:72
Owning exact rational or finite-precision MPFR real, with eager arithmetic.
Definition mpreal.hpp:74
bool empty() const noexcept
Return whether this document has no spans.
Definition presentation.cpp:1182
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
Typed result tables with deferred presentation and numerical export.
presentation::styled_text text
Definition display.cpp:141
constexpr bool isfinite(I const &x) noexcept
Returns whether an integer scalar is finite.
Definition math.hpp:121
Defines basic_half_int<T>, a signed integer type with half-integer resolution.
Scalar math helper utilities.
Definition qnum.hpp:403
Definition display.cpp:16
T parse(std::string_view text)
Definition metadata.hpp:36
Definition data_table.cpp:8
std::map< std::string, std::string > table_metadata
Application metadata is owned text, independent of numerical rows.
Definition data_table.hpp:314
Definition async/async.hpp:26
configuration_source
Definition metadata.hpp:244
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
Definition metadata.hpp:282
std::string unit
Definition metadata.hpp:284
std::string description
Definition metadata.hpp:285
std::string label
Definition metadata.hpp:283
std::optional< value_origin > origin
Definition metadata.hpp:288
Definition metadata.hpp:292
std::string id
Definition metadata.hpp:293
metadata_value value
Definition metadata.hpp:294
metadata_field_options options
Definition metadata.hpp:295
Definition metadata.hpp:299
std::string label
Definition metadata.hpp:301
std::string id
Definition metadata.hpp:300
std::vector< metadata_field > fields
Definition metadata.hpp:302
Project-level numeric limits customization point.
Definition numeric_limits.hpp:16
Column display settings. These never change stored values or default export precision.
Definition data_table.hpp:198
Identifies the winning source, without retaining discarded candidates.
Definition metadata.hpp:275
std::string location
Definition metadata.hpp:277
configuration_source source
Definition metadata.hpp:276
std::string key
Definition metadata.hpp:278