PixelBullet  0.0.1
A C++ game engine
Loading...
Searching...
No Matches
node_reader.h
1#pragma once
2
3#include "pixelbullet/serialization/node.h"
4#include "pixelbullet/serialization/serialization_result.h"
5
6#include <glm/glm.hpp>
7
8#include <charconv>
9#include <cmath>
10#include <concepts>
11#include <cstddef>
12#include <cstdint>
13#include <limits>
14#include <map>
15#include <optional>
16#include <string>
17#include <string_view>
18#include <system_error>
19#include <type_traits>
20#include <utility>
21#include <vector>
22
23namespace pixelbullet::serialization
24{
25[[nodiscard]] inline std::string NodeTypeName(const NodeType type)
26{
27 switch (type)
28 {
29 case NodeType::Null:
30 return "null";
31 case NodeType::Object:
32 return "object";
33 case NodeType::Array:
34 return "array";
35 case NodeType::String:
36 return "string";
37 case NodeType::Boolean:
38 return "boolean";
39 case NodeType::Integer:
40 return "integer";
41 case NodeType::Decimal:
42 return "decimal";
43 }
44 return "unknown";
45}
46
47class NodeReader
48{
49public:
50 explicit NodeReader(std::string root_path = {})
51 : path_(std::move(root_path))
52 {
53 }
54
55 class PathScope
56 {
57 public:
58 PathScope(NodeReader& reader, std::string next_path)
59 : reader_(reader)
60 , previous_path_(std::move(reader.path_))
61 {
62 reader_.path_ = std::move(next_path);
63 }
64
65 PathScope(const PathScope&) = delete;
66 PathScope& operator=(const PathScope&) = delete;
67
68 ~PathScope()
69 {
70 reader_.path_ = std::move(previous_path_);
71 }
72
73 private:
74 NodeReader& reader_;
75 std::string previous_path_;
76 };
77
78 [[nodiscard]] PathScope PushProperty(const std::string_view property)
79 {
80 std::string next = path_.empty() ? std::string(property) : path_ + "." + std::string(property);
81 return PathScope(*this, std::move(next));
82 }
83
84 [[nodiscard]] PathScope PushIndex(const std::size_t index)
85 {
86 std::string next = path_.empty() ? "[" + std::to_string(index) + "]" : path_ + "[" + std::to_string(index) + "]";
87 return PathScope(*this, std::move(next));
88 }
89
90 [[nodiscard]] bool Fail(std::string message)
91 {
92 diagnostics_.push_back(SerializationDiagnostic{ .path = path_, .message = std::move(message) });
93 return false;
94 }
95
96 [[nodiscard]] bool ExpectScalar(const Node& node)
97 {
98 if (node.type() == NodeType::Object || node.type() == NodeType::Array || node.type() == NodeType::Null)
99 {
100 return Fail("Expected scalar node, got " + NodeTypeName(node.type()) + ".");
101 }
102 return true;
103 }
104
105 [[nodiscard]] bool ExpectObjectOrNull(const Node& node)
106 {
107 if (node.type() != NodeType::Object && node.type() != NodeType::Null)
108 {
109 return Fail("Expected object node, got " + NodeTypeName(node.type()) + ".");
110 }
111 return true;
112 }
113
114 [[nodiscard]] bool ExpectArray(const Node& node)
115 {
116 if (node.type() != NodeType::Array)
117 {
118 return Fail("Expected array node, got " + NodeTypeName(node.type()) + ".");
119 }
120 return true;
121 }
122
123 [[nodiscard]] const std::vector<SerializationDiagnostic>& Diagnostics() const noexcept
124 {
125 return diagnostics_;
126 }
127
128 [[nodiscard]] bool HasErrors() const noexcept
129 {
130 return !diagnostics_.empty();
131 }
132
133 [[nodiscard]] SerializationResult ToResult() const
134 {
135 if (diagnostics_.empty())
136 {
137 return SerializationResult{ true, {} };
138 }
139
140 const SerializationDiagnostic& first = diagnostics_.front();
141 std::string message = first.path.empty() ? first.message : first.path + ": " + first.message;
142 return SerializationResult{ false, std::move(message), diagnostics_ };
143 }
144
145private:
146 std::string path_;
147 std::vector<SerializationDiagnostic> diagnostics_;
148};
149
150template <typename T>
151[[nodiscard]] bool TryReadNode(const Node& node, T& value, NodeReader& reader)
152 requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
153{
154 if (!reader.ExpectScalar(node))
155 {
156 return false;
157 }
158
159 const std::string& serialized = node.value();
160 const char* begin = serialized.data();
161 const char* end = begin + serialized.size();
162 T parsed{};
163 const auto result = std::from_chars(begin, end, parsed);
164 if (result.ec != std::errc() || result.ptr != end)
165 {
166 return reader.Fail("Expected integer value, got '" + serialized + "'.");
167 }
168
169 value = parsed;
170 return true;
171}
172
173template <typename T>
174[[nodiscard]] bool TryReadNode(const Node& node, T& value, NodeReader& reader)
175 requires std::is_floating_point_v<T>
176{
177 if (!reader.ExpectScalar(node))
178 {
179 return false;
180 }
181
182 const std::string& serialized = node.value();
183 const char* begin = serialized.data();
184 const char* end = begin + serialized.size();
185 T parsed{};
186 const auto result = std::from_chars(begin, end, parsed);
187 if (result.ec != std::errc() || result.ptr != end || !std::isfinite(static_cast<long double>(parsed)))
188 {
189 return reader.Fail("Expected finite decimal value, got '" + serialized + "'.");
190 }
191
192 value = parsed;
193 return true;
194}
195
196[[nodiscard]] inline bool TryReadNode(const Node& node, bool& value, NodeReader& reader)
197{
198 if (!reader.ExpectScalar(node))
199 {
200 return false;
201 }
202
203 const std::string& serialized = node.value();
204 if (serialized == "true" || serialized == "1")
205 {
206 value = true;
207 return true;
208 }
209 if (serialized == "false" || serialized == "0")
210 {
211 value = false;
212 return true;
213 }
214
215 return reader.Fail("Expected boolean value, got '" + serialized + "'.");
216}
217
218[[nodiscard]] inline bool TryReadNode(const Node& node, std::string& value, NodeReader& reader)
219{
220 if (!reader.ExpectScalar(node))
221 {
222 return false;
223 }
224
225 value = node.value();
226 return true;
227}
228
229[[nodiscard]] inline bool TryReadNode(const Node& node, glm::vec2& value, NodeReader& reader)
230{
231 if (!reader.ExpectArray(node))
232 {
233 return false;
234 }
235 if (node.properties().size() < 2)
236 {
237 return reader.Fail("Expected at least 2 array elements.");
238 }
239
240 glm::vec2 parsed{};
241 bool ok = true;
242 static constexpr std::string_view kComponents[] = { "x", "y" };
243 for (std::size_t index = 0; index < 2; ++index)
244 {
245 auto scope = reader.PushProperty(kComponents[index]);
246 ok &= TryReadNode(node.properties()[index].second, parsed[static_cast<int>(index)], reader);
247 }
248
249 if (ok)
250 {
251 value = parsed;
252 }
253 return ok;
254}
255
256[[nodiscard]] inline bool TryReadNode(const Node& node, glm::vec3& value, NodeReader& reader)
257{
258 if (!reader.ExpectArray(node))
259 {
260 return false;
261 }
262 if (node.properties().size() < 3)
263 {
264 return reader.Fail("Expected at least 3 array elements.");
265 }
266
267 glm::vec3 parsed{};
268 bool ok = true;
269 static constexpr std::string_view kComponents[] = { "x", "y", "z" };
270 for (std::size_t index = 0; index < 3; ++index)
271 {
272 auto scope = reader.PushProperty(kComponents[index]);
273 ok &= TryReadNode(node.properties()[index].second, parsed[static_cast<int>(index)], reader);
274 }
275
276 if (ok)
277 {
278 value = parsed;
279 }
280 return ok;
281}
282
283[[nodiscard]] inline bool TryReadNode(const Node& node, glm::vec4& value, NodeReader& reader)
284{
285 if (!reader.ExpectArray(node))
286 {
287 return false;
288 }
289 if (node.properties().size() < 4)
290 {
291 return reader.Fail("Expected at least 4 array elements.");
292 }
293
294 glm::vec4 parsed{};
295 bool ok = true;
296 static constexpr std::string_view kComponents[] = { "x", "y", "z", "w" };
297 for (std::size_t index = 0; index < 4; ++index)
298 {
299 auto scope = reader.PushProperty(kComponents[index]);
300 ok &= TryReadNode(node.properties()[index].second, parsed[static_cast<int>(index)], reader);
301 }
302
303 if (ok)
304 {
305 value = parsed;
306 }
307 return ok;
308}
309
310template <typename T>
311[[nodiscard]] bool TryReadNode(const Node& node, std::vector<T>& value, NodeReader& reader)
312{
313 if (!reader.ExpectArray(node))
314 {
315 return false;
316 }
317
318 std::vector<T> parsed;
319 parsed.reserve(node.properties().size());
320 bool ok = true;
321 std::size_t index = 0;
322 for (const auto& [key, child] : node.properties())
323 {
324 (void)key;
325 T item{};
326 {
327 auto scope = reader.PushIndex(index);
328 ok &= TryReadNode(child, item, reader);
329 }
330 parsed.push_back(std::move(item));
331 ++index;
332 }
333
334 if (ok)
335 {
336 value = std::move(parsed);
337 }
338 return ok;
339}
340
341template <typename T>
342[[nodiscard]] bool TryReadNode(const Node& node, std::map<std::string, T>& value, NodeReader& reader)
343{
344 if (!reader.ExpectObjectOrNull(node))
345 {
346 return false;
347 }
348
349 std::map<std::string, T> parsed;
350 bool ok = true;
351 for (const auto& [key, child] : node.properties())
352 {
353 T item{};
354 {
355 auto scope = reader.PushProperty(key);
356 ok &= TryReadNode(child, item, reader);
357 }
358 parsed.emplace(key, std::move(item));
359 }
360
361 if (ok)
362 {
363 value = std::move(parsed);
364 }
365 return ok;
366}
367
368template <typename T>
369[[nodiscard]] bool TryReadNode(const Node& node, std::optional<T>& value, NodeReader& reader)
370{
371 if (node.type() == NodeType::Null || !node.is_valid())
372 {
373 value.reset();
374 return true;
375 }
376
377 T parsed{};
378 if (!TryReadNode(node, parsed, reader))
379 {
380 return false;
381 }
382
383 value = std::move(parsed);
384 return true;
385}
386
387template <typename T, typename ParseFn>
388[[nodiscard]] bool TryReadEnumToken(const Node& node, T& value, NodeReader& reader, ParseFn&& parse_fn, const std::string_view enum_name)
389{
390 std::string token;
391 if (!TryReadNode(node, token, reader))
392 {
393 return false;
394 }
395
396 T parsed{};
397 if (!parse_fn(token, parsed))
398 {
399 return reader.Fail("Unknown " + std::string(enum_name) + " token '" + token + "'.");
400 }
401
402 value = parsed;
403 return true;
404}
405
406template <typename T>
407[[nodiscard]] bool TryReadRequiredProperty(const Node& node, const std::string_view property, T& value, NodeReader& reader)
408{
409 if (!reader.ExpectObjectOrNull(node))
410 {
411 return false;
412 }
413
414 const Node* child = node.get_property(std::string(property));
415 auto scope = reader.PushProperty(property);
416 if (child == nullptr)
417 {
418 return reader.Fail("Missing required property.");
419 }
420 return TryReadNode(*child, value, reader);
421}
422
423template <typename T>
424[[nodiscard]] bool TryReadOptionalProperty(const Node& node, const std::string_view property, T& value, NodeReader& reader)
425{
426 if (!reader.ExpectObjectOrNull(node))
427 {
428 return false;
429 }
430
431 const Node* child = node.get_property(std::string(property));
432 if (child == nullptr)
433 {
434 return true;
435 }
436
437 auto scope = reader.PushProperty(property);
438 return TryReadNode(*child, value, reader);
439}
440
441template <typename T>
442[[nodiscard]] bool TryReadOptionalProperty(const Node& node, const std::string_view property, T& value, const T& default_value,
443 NodeReader& reader)
444{
445 if (!reader.ExpectObjectOrNull(node))
446 {
447 return false;
448 }
449
450 if (!node.has_property(std::string(property)))
451 {
452 value = default_value;
453 return true;
454 }
455 return TryReadOptionalProperty(node, property, value, reader);
456}
457
458template <typename T>
459concept NodeReadable = requires(const Node& node, T& value, NodeReader& reader) {
460 { TryReadNode(node, value, reader) } -> std::same_as<bool>;
461};
462} // namespace pixelbullet::serialization
Represents a hierarchical node capable of storing various data types and supporting YAML serializatio...
Definition node.h:49
Definition node_reader.h:48
Definition node_reader.h:459
Definition serialization_result.h:9