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