CbmRoot
Loading...
Searching...
No Matches
CbmYaml.h
Go to the documentation of this file.
1/* Copyright (C) 2023-2025 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
2 SPDX-License-Identifier: GPL-3.0-only
3 Authors: Felix Weiglhofer [committer] */
4#ifndef CBM_YAML_YAML_H
5#define CBM_YAML_YAML_H
6#pragma once
7
8#include "CbmEnumDict.h"
9#include "CbmYamlBaseTypes.h"
10#include "CbmYamlProperty.h"
11
12#include <boost/filesystem.hpp>
13
14#include <iostream>
15#include <sstream>
16#include <string_view>
17
18#include <fmt/format.h>
19#include <yaml-cpp/yaml.h>
21{
22 namespace fs = boost::filesystem;
23
24 // typealias for Rust-like fixed size integer types
25 using i8 = int8_t;
26 using u8 = uint8_t;
27 using i16 = int16_t;
28 using u16 = uint16_t;
29 using i32 = int32_t;
30 using u32 = uint32_t;
31 using i64 = int64_t;
32 using u64 = uint64_t;
33 using f32 = float;
34 using f64 = double;
35
36 template<typename T>
37 T Read(const YAML::Node& node);
38
39 template<typename T, T... Values, typename Func>
40 constexpr void ForEach(std::integer_sequence<T, Values...>, Func&& func)
41 {
42 (func(std::integral_constant<T, Values>{}), ...);
43 }
44
45 template<typename T, typename = void>
46 struct GetFmtTag {
47 static constexpr std::optional<YAML::EMITTER_MANIP> value = {};
48 };
49
50 template<typename T>
51 struct GetFmtTag<T, std::void_t<decltype(T::FormatAs)>> {
52 static constexpr std::optional<YAML::EMITTER_MANIP> value = T::FormatAs;
53 };
54
55 template<typename T, typename = void>
57 static constexpr bool value = false;
58 };
59
60 template<typename T>
61 struct ShouldMergeProperty<T, std::void_t<decltype(T::MergeProperty)>> {
62 static constexpr bool value = T::MergeProperty;
63 };
64
65 template<typename T>
66 T ReadFromFile(fs::path path)
67 {
68 YAML::Node node = YAML::LoadFile(path.string());
69 return Read<T>(node);
70 }
71
72
73 template<typename T>
74 T Read(const YAML::Node& node)
75 {
76// Disable uninitialized warning for the whole function.
77// GCC 11.4 sometimes incorrectly warns about uninitialized variables in constexpr if branches.
78// I'm fairly certain this is a compiler bug, because it only happens when
79// explicitly instantiating the function template. And the error message
80// references a variable 't' that doesn't exist in the function!
81#if defined(__GNUC__) && !defined(__clang__)
82#pragma GCC diagnostic push
83#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
84#endif
85 using Type = std::remove_cv_t<std::remove_reference_t<T>>;
86
87 static_assert(!IsEnum<T> || util::detail::EnumHasDict_v<T>, "Enum must have a dictionary to be deserializable");
88
89 // TODO: error handling
90 if constexpr (IsFundamental<Type>) {
91 return node.as<Type>();
92 }
93 else if constexpr (IsOptional<Type>) {
94 if (node) {
95 return std::make_optional(Read<typename Type::value_type>(node));
96 }
97 else {
98 return std::nullopt;
99 }
100 }
101 else if constexpr (IsEnum<Type>) {
102 std::optional<T> maybet = util::FromString<T>(node.as<std::string>());
103 if (!maybet) {
104 throw std::runtime_error(fmt::format("Invalid enum value: {}", node.as<std::string>()));
105 }
106 return *maybet;
107 }
108 else if constexpr (IsVector<Type>) {
109 Type vector;
110 for (const auto& element : node) {
111 vector.push_back(Read<typename Type::value_type>(element));
112 }
113 return vector;
114 }
115 else if constexpr (IsArray<Type>) {
116 Type array = {};
118 if (vector.size() != array.size()) {
119 throw std::runtime_error(fmt::format("Array size mismatch: expected {}, got {}", array.size(), vector.size()));
120 }
121 std::copy(vector.begin(), vector.end(), array.begin());
122 return array;
123 }
124 else if constexpr (IsSet<Type>) {
125 Type set;
126 for (const auto& element : node) {
127 set.insert(Read<typename Type::value_type>(element));
128 }
129 return set;
130 }
131 else if constexpr (IsMap<Type>) {
132 using Key_t = typename Type::key_type;
133 using Val_t = typename Type::mapped_type;
134
135 static_assert(IsScalar<Key_t>, "Map key must be a fundamental or enum type");
136
137 Type map{};
138 for (YAML::const_iterator it = node.begin(); it != node.end(); ++it) {
139 const auto& key = (*it).first;
140 const auto& value = (*it).second;
141 map[Read<Key_t>(key)] = Read<Val_t>(value);
142 }
143 return map;
144 }
145 else {
146 Type object{};
147
148 constexpr auto nProperties = std::tuple_size<decltype(Type::Properties)>::value;
149
150 if constexpr (nProperties == 1 && ShouldMergeProperty<T>::value) {
151 auto& property = std::get<0>(Type::Properties);
152 using ValueType = std::remove_cv_t<std::remove_reference_t<decltype(property.Get(std::declval<Type>()))>>;
153 ValueType& value = property.Get(object);
154 value = Read<ValueType>(node);
155 }
156 else {
157 ForEach(std::make_integer_sequence<std::size_t, nProperties>{}, [&](auto index) {
158 auto& property = std::get<index>(Type::Properties);
159 using ValueType = std::remove_cv_t<std::remove_reference_t<decltype(property.Get(object))>>;
160 ValueType& value = property.Get(object);
161 value = Read<ValueType>(node[std::string{property.Key()}]);
162 });
163 }
164
165 return object;
166 }
167
168#if defined(__GNUC__) && !defined(__clang__)
169#pragma GCC diagnostic pop
170#endif
171 }
172
173 template<typename T>
174 std::string MakeDocString(int indent = 0)
175 {
176 using Type = std::remove_cv_t<std::remove_reference_t<T>>;
177
178 std::stringstream docString;
179
180 if constexpr (IsFundamental<Type>) {
181 docString << Typename<Type>();
182 }
183 else if constexpr (IsVector<Type> || IsArray<Type>) {
184 using ChildType = typename Type::value_type;
185 if constexpr (IsFundamental<ChildType>) {
186 docString << std::string(indent, ' ') << "list of " << Typename<ChildType>() << std::endl;
187 }
188 else {
189 docString << std::string(indent, ' ') << "list of" << std::endl;
190 docString << MakeDocString<ChildType>(indent + 2);
191 }
192 }
193 else {
194 constexpr auto nProperties = std::tuple_size<decltype(Type::Properties)>::value;
195
196 ForEach(std::make_integer_sequence<std::size_t, nProperties>{}, [&](auto index) {
197 using ChildType = std::remove_cv_t<
198 std::remove_reference_t<decltype(std::get<index>(Type::Properties).Get(std::declval<Type>()))>>;
199 auto& property = std::get<index>(Type::Properties);
200 if constexpr (IsFundamental<ChildType>) {
201 docString << std::string(indent, ' ') << property.Key() << ": " << property.Description() << " ["
202 << Typename<ChildType>() << "]" << std::endl;
203 }
204 else {
205 docString << std::string(indent, ' ') << property.Key() << ": " << property.Description() << std::endl;
206 docString << MakeDocString<ChildType>(indent + 2);
207 }
208 });
209 }
210 return docString.str();
211 }
212
213 class Dump {
214
215 public:
220 template<typename T>
221 std::string operator()(const T& object, int floatPrecision = 6)
222 {
223 YAML::Emitter ss;
224 ss << YAML::BeginDoc;
225 ss << YAML::Precision(floatPrecision);
226 DoDump(object, ss);
227 ss << YAML::EndDoc;
228 return ss.c_str();
229 }
230
231 template<typename T>
232 void DoDump(const T& object, YAML::Emitter& ss, std::optional<YAML::EMITTER_MANIP> formatEntries = {})
233 {
234 static_assert(!IsEnum<T> || util::detail::EnumHasDict_v<T>, "Enum must have a dictionary");
235
236 if constexpr (IsFundamental<T>) {
237 // Take care that i8 and u8 are printed as integers not as characters
238 if constexpr (std::is_same_v<T, i8> || std::is_same_v<T, u8>)
239 ss << i32(object);
240 else
241 ss << object;
242 }
243 else if constexpr (IsOptional<T>) {
244 if (object.has_value()) {
245 DoDump(object.value(), ss);
246 }
247 }
248 else if constexpr (IsEnum<T>) {
249 ss << std::string{util::ToString<T>(object)};
250 }
251 else if constexpr (IsVector<T> || IsArray<T> || IsSet<T>) {
252 ss << YAML::BeginSeq;
253 // Special case for vector<bool> because it is not a real vector
254 // Clang does not the compile the generic version of the loop
255 // in this case.
256 if constexpr (std::is_same_v<T, std::vector<bool>>) {
257 for (bool element : object) {
258 if (formatEntries.has_value()) {
259 ss << formatEntries.value();
260 }
261 ss << element;
262 }
263 }
264 else {
265 for (const auto& element : object) {
266 if (formatEntries.has_value()) {
267 ss << formatEntries.value();
268 }
269 DoDump(element, ss);
270 }
271 }
272 ss << YAML::EndSeq;
273 }
274 else if constexpr (IsMap<T>) {
275 ss << YAML::BeginMap;
276 for (const auto& [key, value] : object) {
277 if (formatEntries.has_value()) {
278 ss << formatEntries.value();
279 }
280 if constexpr (IsOptional<std::remove_cv_t<std::remove_reference_t<decltype(value)>>>) {
281 if (value.has_value()) {
282 ss << YAML::Key << key;
283 }
284 }
285 else {
286 ss << YAML::Key << key;
287 }
288 ss << YAML::Value;
289 DoDump(value, ss);
290 }
291 ss << YAML::EndMap;
292 }
293 else {
294 constexpr auto nProperties = std::tuple_size<decltype(T::Properties)>::value;
295 if (auto fmtTag = GetFmtTag<T>::value; fmtTag.has_value()) {
296 ss << fmtTag.value();
297 }
298
299 if constexpr (nProperties == 1 && ShouldMergeProperty<T>::value) {
300 auto& property = std::get<0>(T::Properties);
301 auto& value = property.Get(object);
302 auto format = property.Format();
303 if (format.has_value()) {
304 ss << format.value();
305 }
306 DoDump(value, ss, property.FormatEntries());
307 }
308 else {
309 ss << YAML::BeginMap;
310 ForEach(std::make_integer_sequence<std::size_t, nProperties>{}, [&](auto index) {
311 auto& property = std::get<index>(T::Properties);
312 auto& value = property.Get(object);
313 auto format = property.Format();
314 if constexpr (IsOptional<std::remove_cv_t<std::remove_reference_t<decltype(value)>>>) {
315 if (value.has_value()) {
316 ss << YAML::Key << std::string{property.Key()};
317 }
318 }
319 else {
320 ss << YAML::Key << std::string{property.Key()};
321 }
322 if (format.has_value()) {
323 ss << format.value();
324 }
325 ss << YAML::Value;
326 DoDump(value, ss, property.FormatEntries());
327 });
328 ss << YAML::EndMap;
329 }
330 }
331 }
332 };
333
334} // namespace cbm::util::yaml
335
342#define CBM_YAML_EXTERN_DECL(type) \
343 extern template type cbm::util::yaml::Read<type>(const YAML::Node& node); \
344 extern template std::string cbm::util::yaml::Dump::operator()<type>(const type& value, int floatPrecision); \
345 extern template void cbm::util::yaml::Dump::DoDump<type>(const type& object, YAML::Emitter& ss, \
346 std::optional<YAML::EMITTER_MANIP> formatEntries);
347
352#define CBM_YAML_INSTANTIATE(type) \
353 template type cbm::util::yaml::Read<type>(const YAML::Node& node); \
354 template std::string cbm::util::yaml::Dump::operator()<type>(const type& value, int floatPrecision); \
355 template void cbm::util::yaml::Dump::DoDump<type>(const type& object, YAML::Emitter& ss, \
356 std::optional<YAML::EMITTER_MANIP> formatEntries);
357
358namespace cbm::algo
359{
360 namespace yaml = util::yaml;
361};
362
363#endif // CBM_YAML_YAML_H
std::string operator()(const T &object, int floatPrecision=6)
Dumps an object into YAML-formatted string.
Definition CbmYaml.h:221
void DoDump(const T &object, YAML::Emitter &ss, std::optional< YAML::EMITTER_MANIP > formatEntries={})
Definition CbmYaml.h:232
constexpr bool EnumHasDict_v
Definition CbmEnumDict.h:34
constexpr bool IsArray
uint64_t u64
Definition CbmYaml.h:32
constexpr void ForEach(std::integer_sequence< T, Values... >, Func &&func)
Definition CbmYaml.h:40
constexpr bool IsEnum
int64_t i64
Definition CbmYaml.h:31
constexpr bool IsFundamental
constexpr bool IsMap
std::string MakeDocString(int indent=0)
Definition CbmYaml.h:174
T ReadFromFile(fs::path path)
Definition CbmYaml.h:66
uint16_t u16
Definition CbmYaml.h:28
constexpr bool IsSet
constexpr bool IsOptional
T Read(const YAML::Node &node)
Definition CbmYaml.h:74
int32_t i32
Definition CbmYaml.h:29
int16_t i16
Definition CbmYaml.h:27
constexpr std::string_view Typename()
constexpr bool IsScalar
constexpr bool IsVector
uint32_t u32
Definition CbmYaml.h:30
uint8_t u8
Definition CbmYaml.h:26
std::optional< T > FromString(std::string_view str, bool caseSensitive=false)
Definition CbmEnumDict.h:50
std::string_view ToString(T t)
Definition CbmEnumDict.h:64
Hash for CbmL1LinkKey.
static constexpr std::optional< YAML::EMITTER_MANIP > value
Definition CbmYaml.h:52
static constexpr std::optional< YAML::EMITTER_MANIP > value
Definition CbmYaml.h:47
static constexpr bool value
Definition CbmYaml.h:57