uzuki2
Recovering R lists faithfully from HDF5 or JSON
Loading...
Searching...
No Matches
parse_hdf5.hpp
Go to the documentation of this file.
1#ifndef UZUKI2_PARSE_HPP
2#define UZUKI2_PARSE_HPP
3
4#include <memory>
5#include <vector>
6#include <string>
7#include <cstring>
8#include <stdexcept>
9#include <exception>
10#include <cstdint>
11#include <unordered_set>
12
13#include "H5Cpp.h"
14
15#include "ritsuko/ritsuko.hpp"
17
18#include "interfaces.hpp"
19#include "Dummy.hpp"
20#include "ExternalTracker.hpp"
21#include "Version.hpp"
22#include "ParsedList.hpp"
23
29namespace uzuki2 {
30
39namespace hdf5 {
40
44struct Options {
50
54 bool strict_list = true;
55};
56
60inline void validate_numeric_missing_placeholder(const H5::Attribute& attr, const H5::DataSet& data, const Version& version) {
61 if (attr.getSpace().getSimpleExtentNdims() != 0) {
62 throw std::runtime_error("expected the missing value placeholder to be a scalar attribute");
63 }
64 if (version.lt(1, 2)) {
65 if (attr.getDataType().getClass() != data.getDataType().getClass()) {
66 throw std::runtime_error("expected the missing value placeholder attribute to have the same type class as its dataset");
67 }
68 } else {
69 if (attr.getDataType() != data.getDataType()) {
70 throw std::runtime_error("expected the missing value placeholder attribute to have the same type as its dataset");
71 }
72 }
73}
74
75inline void validate_string_missing_placeholder(const H5::Attribute& attr) {
76 if (attr.getSpace().getSimpleExtentNdims() != 0) {
77 throw std::runtime_error("expected the missing value placeholder attribute to be a scalar");
78 }
79 if (!ritsuko::hdf5::is_utf8_string(attr)) {
80 throw std::runtime_error("expected the missing value placeholder attribute to be a UTF-8 string");
81 }
82}
83
84template<typename Type_, class Stream_, class Action_>
85void iterate_stream(Stream_& stream, Action_ action) {
86 auto buffer = sanisizer::create<std::vector<Type_> >(stream.chunk_size());
87 while (true) {
88 auto available = stream.load(buffer.data());
89 if (available == 0) {
90 break;
91 }
92 for (I<decltype(available)> i = 0; i < available; ++i) {
93 action(i + stream.start(), std::move(buffer[i]));
94 }
95 }
96}
97
98template<class Host_, class Function_>
99void parse_integer_like(const H5::DataSet& handle, Host_* ptr, bool is_scalar, Function_ check, const Version& version, const Options& options) {
100 if (ritsuko::hdf5::exceeds_integer_limit(handle, 32, true)) {
101 throw std::runtime_error("dataset cannot be represented by 32-bit signed integers");
102 }
103
104 bool has_missing = false;
105 std::int32_t missing_value = -2147483648;
106 if (version.equals(1, 0)) {
107 has_missing = true;
108 } else {
109 const char* placeholder_name = "missing-value-placeholder";
110 has_missing = handle.attrExists(placeholder_name);
111 if (has_missing) {
112 try {
113 auto attr = handle.openAttribute(placeholder_name);
114 validate_numeric_missing_placeholder(attr, handle, version);
115 attr.read(H5::PredType::NATIVE_INT32, &missing_value);
116 } catch (...) {
117 std::throw_with_nested(std::runtime_error("failed to read the '" + std::string(placeholder_name) + "' attribute"));
118 }
119 }
120 }
121
122 auto set = [&](hsize_t i, std::int32_t x) -> void {
123 if (has_missing && x == missing_value) {
124 ptr->set_missing(i);
125 } else {
126 check(x);
127 ptr->set(i, x);
128 }
129 };
130
131 if (is_scalar) {
132 std::int32_t value;
133 handle.read(&value, H5::PredType::NATIVE_INT32);
134 set(0, value);
135 } else {
136 ritsuko::hdf5::Stream1dNumericDataset<std::int32_t> stream(
137 &handle,
138 static_cast<hsize_t>(ptr->size()), // cast is safe, as ptr would have been initially allocated iwith an hsize_t length.
139 [&]{
140 ritsuko::hdf5::Stream1dNumericDatasetOptions opt;
141 opt.contiguous_chunk_size = options.buffer_size;
142 return opt;
143 }()
144 );
145 iterate_stream<std::int32_t>(stream, set);
146 }
147}
148
149template<class Host_, class Function_>
150void parse_string_like(const H5::DataSet& handle, Host_* ptr, bool is_scalar, Function_ check, const Options& options) {
151 if (!ritsuko::hdf5::is_utf8_string(handle)) {
152 throw std::runtime_error("expected a datatype that can be represented by a UTF-8 string");
153 }
154
155 std::optional<std::string> missingness;
156 const char* placeholder_name = "missing-value-placeholder";
157 if (handle.attrExists(placeholder_name)) {
158 try {
159 auto attr = handle.openAttribute(placeholder_name);
160 validate_string_missing_placeholder(attr);
161 missingness = ritsuko::hdf5::read_scalar_string(attr);
162 } catch (...) {
163 std::throw_with_nested(std::runtime_error("failed to read the '" + std::string(placeholder_name) + "' attribute"));
164 }
165 }
166
167 auto set = [&](hsize_t i, std::string x) -> void {
168 if (missingness.has_value() && x == *missingness) {
169 ptr->set_missing(i);
170 } else {
171 check(x);
172 ptr->set(i, std::move(x));
173 }
174 };
175
176 if (is_scalar) {
177 auto x = ritsuko::hdf5::read_scalar_string(handle);
178 set(0, std::move(x));
179 } else {
180 ritsuko::hdf5::Stream1dStringDataset stream(
181 &handle,
182 static_cast<hsize_t>(ptr->size()), // cast is safe, as ptr would have been initially allocated iwith an hsize_t length.
183 [&]{
184 ritsuko::hdf5::Stream1dStringDatasetOptions opt;
185 opt.contiguous_chunk_size = options.buffer_size;
186 return opt;
187 }()
188 );
189 iterate_stream<std::string>(stream, set);
190 }
191}
192
193inline double r_missing_value() {
194 std::uint32_t tmp_value = 1;
195 auto tmp_ptr = reinterpret_cast<unsigned char*>(&tmp_value);
196
197 // Mimic R's generation of these values, but we can't use type punning as
198 // this is not legal in C++, and we don't have bit_cast yet.
199 double missing_value = 0;
200 auto missing_ptr = reinterpret_cast<unsigned char*>(&missing_value);
201
202 int step = 1;
203 if (tmp_ptr[0] == 1) { // little-endian.
204 missing_ptr += sizeof(double) - 1;
205 step = -1;
206 }
207
208 *missing_ptr = 0x7f;
209 *(missing_ptr += step) = 0xf0;
210 *(missing_ptr += step) = 0x00;
211 *(missing_ptr += step) = 0x00;
212 *(missing_ptr += step) = 0x00;
213 *(missing_ptr += step) = 0x00;
214 *(missing_ptr += step) = 0x07;
215 *(missing_ptr += step) = 0xa2;
216
217 return missing_value;
218}
219
220template<class Host_, class Function_>
221void parse_numbers(const H5::DataSet& handle, Host_* ptr, bool is_scalar, Function_ check, const Version& version, const Options& options) {
222 if (version.lt(1, 3)) {
223 if (handle.getTypeClass() != H5T_FLOAT) {
224 throw std::runtime_error("expected a floating-point dataset");
225 }
226 } else {
227 if (ritsuko::hdf5::exceeds_float_limit(handle, 64)) {
228 throw std::runtime_error("dataset cannot be represented by 64-bit floats");
229 }
230 }
231
232 // Check that we support IEEE754-compliant floats.
233 static_assert(std::numeric_limits<double>::is_iec559);
234
235 bool has_missing = false;
236 double missing_value = 0;
237 if (version.equals(1, 0)) {
238 has_missing = true;
239 missing_value = r_missing_value();
240 } else {
241 const char* placeholder_name = "missing-value-placeholder";
242 has_missing = handle.attrExists(placeholder_name);
243 if (has_missing) {
244 try {
245 auto attr = handle.openAttribute(placeholder_name);
246 validate_numeric_missing_placeholder(attr, handle, version);
247 attr.read(H5::PredType::NATIVE_DOUBLE, &missing_value);
248 } catch (...) {
249 std::throw_with_nested(std::runtime_error("failed to read the '" + std::string(placeholder_name) + "' attribute"));
250 }
251 }
252 }
253
254 bool should_compare_nan = version.lt(1, 3);
255 bool is_placeholder_nan = std::isnan(missing_value);
256 auto is_missing_value = [&](double val) -> bool {
257 if (should_compare_nan) {
258 auto xptr = reinterpret_cast<const unsigned char*>(&missing_value);
259 auto yptr = reinterpret_cast<const unsigned char*>(&val);
260 return std::memcmp(xptr, yptr, sizeof(double)) == 0;
261 } else if (is_placeholder_nan) {
262 return std::isnan(val);
263 } else {
264 return val == missing_value;
265 }
266 };
267
268 auto set = [&](hsize_t i, double x) -> void {
269 if (has_missing && is_missing_value(x)) {
270 ptr->set_missing(i);
271 } else {
272 check(x);
273 ptr->set(i, x);
274 }
275 };
276
277 if (is_scalar) {
278 double val;
279 handle.read(&val, H5::PredType::NATIVE_DOUBLE);
280 set(0, val);
281 } else {
282 ritsuko::hdf5::Stream1dNumericDataset<double> stream(
283 &handle,
284 static_cast<hsize_t>(ptr->size()), // cast is safe, as ptr would have been initially allocated iwith an hsize_t length.
285 [&]{
286 ritsuko::hdf5::Stream1dNumericDatasetOptions opt;
287 opt.contiguous_chunk_size = options.buffer_size;
288 return opt;
289 }()
290 );
291 iterate_stream<double>(stream, set);
292 }
293}
294
295template<class Host_>
296void parse_names(const H5::DataSet& nhandle, Host_* ptr, const Options& options) {
297 if (!ritsuko::hdf5::is_utf8_string(nhandle)) {
298 throw std::runtime_error("expected a datatype that can be represented by a UTF-8 string");
299 }
300
301 const auto space = nhandle.getSpace();
302 if (space.getSimpleExtentNdims() != 1) {
303 throw std::runtime_error("expected a 1-dimensional dataset");
304 }
305 hsize_t nlen;
306 space.getSimpleExtentDims(&nlen);
307 if (!sanisizer::is_equal(nlen, ptr->size())) {
308 throw std::runtime_error("number of names should be equal to the object length");
309 }
310
311 ritsuko::hdf5::Stream1dStringDataset stream(
312 &nhandle,
313 nlen,
314 [&]{
315 ritsuko::hdf5::Stream1dStringDatasetOptions opt;
316 opt.contiguous_chunk_size = options.buffer_size;
317 return opt;
318 }()
319 );
320
321 iterate_stream<std::string>(
322 stream,
323 [&](hsize_t pos, std::string val) -> void {
324 ptr->set_name(pos, std::move(val));
325 }
326 );
327}
328
329inline std::string safe_read_scalar_string_attribute(const H5::Attribute& attr) {
330 if (attr.getSpace().getSimpleExtentNdims() != 0) {
331 throw std::runtime_error("expected attribute to be scalar");
332 }
333 if (!ritsuko::hdf5::is_utf8_string(attr)) {
334 throw std::runtime_error("expected attribute to be a UTF-8 string");
335 }
336 return ritsuko::hdf5::read_scalar_string(attr);
337}
338
339template<class Provisioner_, class Externals_>
340std::shared_ptr<Base> parse_inner(const H5::Group& handle, Externals_& ext, const Version& version, const Options& options) {
341 std::string object_type;
342 try {
343 auto ahandle = handle.openAttribute("uzuki_object");
344 object_type = safe_read_scalar_string_attribute(ahandle);
345 } catch (...) {
346 std::throw_with_nested(std::runtime_error("failed to read the 'uzuki_object' attribute"));
347 }
348
349 std::shared_ptr<Base> output;
350
351 if (object_type == "list") {
352 const bool named = handle.exists("names");
353
354 try {
355 auto dhandle = handle.openGroup("data");
356 const auto len = dhandle.getNumObjs();
357 auto lptr = Provisioner_::new_List(sanisizer::cast<std::size_t>(len), named);
358 output.reset(lptr);
359
360 for (I<decltype(len)> i = 0; i < len; ++i) {
361 const auto istr = ritsuko::itoa(i);
362 try {
363 auto lhandle = dhandle.openGroup(istr);
364 lptr->set(i, parse_inner<Provisioner_>(lhandle, ext, version, options));
365 } catch (...) {
366 std::throw_with_nested(std::runtime_error("failed to read element " + istr));
367 }
368 }
369 } catch (...) {
370 std::throw_with_nested(std::runtime_error("failed to read 'data'"));
371 }
372
373 if (named) {
374 auto lptr = static_cast<List*>(output.get());
375 try {
376 auto nhandle = handle.openDataSet("names");
377 parse_names(nhandle, lptr, options);
378 } catch (...) {
379 std::throw_with_nested(std::runtime_error("failed to read 'names'"));
380 }
381 }
382
383 } else if (object_type == "vector") {
384 std::string vector_type;
385 try {
386 auto ahandle = handle.openAttribute("uzuki_type");
387 vector_type = safe_read_scalar_string_attribute(ahandle);
388 } catch (...) {
389 std::throw_with_nested(std::runtime_error("failed to read the 'uzuki_type' attribute"));
390 }
391
392 const bool named = handle.exists("names");
393 enum Failure { DATA, ORDERED, LEVELS, HEAP, FORMAT };
394 Failure who_failed = DATA;
395
396 try {
397 auto dhandle = handle.openDataSet("data");
398 const auto dspace = dhandle.getSpace();
399 const auto ndims = dspace.getSimpleExtentNdims();
400
401 hsize_t len = 1;
402 bool is_scalar = false;
403 if (ndims == 0) {
404 is_scalar = true;
405 } else if (ndims == 1) {
406 dspace.getSimpleExtentDims(&len);
407 } else {
408 throw std::runtime_error("expected a scalar or 1-dimensional dataset");
409 }
410
411 if (vector_type == "integer") {
412 auto iptr = Provisioner_::new_Integer(sanisizer::cast<std::size_t>(len), named, is_scalar);
413 output.reset(iptr);
414 parse_integer_like(
415 dhandle,
416 iptr,
417 is_scalar,
418 [](std::int32_t) -> void {},
419 version,
420 options
421 );
422
423 } else if (vector_type == "boolean") {
424 auto bptr = Provisioner_::new_Boolean(sanisizer::cast<std::size_t>(len), named, is_scalar);
425 output.reset(bptr);
426 parse_integer_like(
427 dhandle,
428 bptr,
429 is_scalar,
430 [&](std::int32_t x) -> void {
431 if (x != 0 && x != 1) {
432 throw std::runtime_error("boolean values should be 0 or 1");
433 }
434 },
435 version,
436 options
437 );
438
439 } else if (vector_type == "factor" || (version.equals(1, 0) && vector_type == "ordered")) {
440 who_failed = ORDERED;
441 bool ordered = false;
442 if (vector_type == "ordered") {
443 ordered = true;
444 } else if (handle.exists("ordered")) {
445 try {
446 auto ohandle = handle.openDataSet("ordered");
447 if (ohandle.getSpace().getSimpleExtentNdims() != 0) {
448 throw std::runtime_error("expected 'ordered' to be a scalar dataset");
449 }
450 if (ritsuko::hdf5::exceeds_integer_limit(ohandle, 32, true)) {
451 throw std::runtime_error("'ordered' value cannot be represented by a 32-bit integer");
452 }
453 std::int32_t tmp_ordered = 0;
454 ohandle.read(&tmp_ordered, H5::PredType::NATIVE_INT32);
455 ordered = tmp_ordered > 0;
456 } catch (...) {
457 std::throw_with_nested(std::runtime_error("failed to validate 'ordered'"));
458 }
459 }
460
461 who_failed = LEVELS;
462 auto levhandle = handle.openDataSet("levels");
463 if (!ritsuko::hdf5::is_utf8_string(levhandle)) {
464 throw std::runtime_error("expected a datatype that can be represented by a UTF-8 string for 'levels'");
465 }
466 auto lspace = levhandle.getSpace();
467 if (lspace.getSimpleExtentNdims() != 1) {
468 throw std::runtime_error("expected a 1-dimensional dataset for 'levels'");
469 }
470 hsize_t levlen;
471 lspace.getSimpleExtentDims(&levlen);
472
473 auto fptr = Provisioner_::new_Factor(sanisizer::cast<std::size_t>(len), named, is_scalar, sanisizer::cast<std::size_t>(levlen), ordered);
474 output.reset(fptr);
475
476 std::unordered_set<std::string> present;
477 ritsuko::hdf5::Stream1dStringDataset stream(
478 &levhandle,
479 levlen,
480 [&]{
481 ritsuko::hdf5::Stream1dStringDatasetOptions opt;
482 opt.contiguous_chunk_size = options.buffer_size;
483 return opt;
484 }()
485 );
486
487 iterate_stream<std::string>(
488 stream,
489 [&](hsize_t pos, std::string val) -> void {
490 if (present.find(val) != present.end()) {
491 throw std::runtime_error("detected duplicated factor level '" + val + "'");
492 }
493 fptr->set_level(pos, val);
494 present.insert(std::move(val));
495 }
496 );
497
498 who_failed = DATA;
499 parse_integer_like(
500 dhandle,
501 fptr,
502 is_scalar,
503 [&](std::int32_t x) -> void {
504 if (x < 0) {
505 throw std::runtime_error("factor codes should be non-negative");
506 } else if (sanisizer::is_greater_than_or_equal(x, levlen)) {
507 throw std::runtime_error("factor codes should be less than the number of levels");
508 }
509 },
510 version,
511 options
512 );
513
514 } else if (vector_type == "vls" && !version.lt(1, 4)) {
515 constexpr auto precision = std::numeric_limits<std::uint64_t>::digits;
516 ritsuko::cvls::validate_pointer_datatype(dhandle, precision, precision);
517
518 who_failed = HEAP;
519 auto hhandle = handle.openDataSet("heap");
520 auto hlen = ritsuko::cvls::validate_heap(hhandle);
521
522 who_failed = DATA;
523 const char* placeholder_name = "missing-value-placeholder";
524 std::optional<std::string> missingness;
525 if (dhandle.attrExists(placeholder_name)) {
526 try {
527 auto attr = dhandle.openAttribute(placeholder_name);
528 validate_string_missing_placeholder(attr);
529 missingness = ritsuko::hdf5::read_scalar_string(attr);
530 } catch (...) {
531 std::throw_with_nested(std::runtime_error("failed to validate the '" + std::string(placeholder_name) + "'"));
532 }
533 }
534
535 auto ptr = Provisioner_::new_String(sanisizer::cast<std::size_t>(len), named, is_scalar, StringVector::NONE);
536 output.reset(ptr);
537
538 auto set = [&](hsize_t i, std::string x) -> void {
539 if (missingness.has_value() && x == *missingness) {
540 ptr->set_missing(i);
541 } else {
542 ptr->set(i, std::move(x));
543 }
544 };
545
546 if (is_scalar) {
547 ritsuko::cvls::Pointer<std::uint64_t, std::uint64_t> vlsptr;
548 dhandle.read(&vlsptr, ritsuko::cvls::define_pointer_datatype<std::uint64_t, std::uint64_t>());
549 if (ritsuko::cvls::is_Pointer_out_of_range(vlsptr, hlen)) {
550 throw std::runtime_error("compressed VLS pointer is out of range of the heap");
551 }
552
553 H5::DataSpace dspace(1, &hlen);
554 const hsize_t len = vlsptr.length; // cast is safe if pointer is within range.
555 const hsize_t offset = vlsptr.offset;
556 dspace.selectHyperslab(H5S_SELECT_SET, &len, &offset);
557 H5::DataSpace mspace(1, &len);
558
559 std::vector<std::uint8_t> buffer(vlsptr.length);
560 hhandle.read(buffer.data(), H5::PredType::NATIVE_UINT8, mspace, dspace);
561 auto cptr = reinterpret_cast<const char*>(buffer.data());
562 set(0, std::string(cptr, cptr + ritsuko::strnlen(cptr, vlsptr.length)));
563
564 } else {
565 ritsuko::cvls::Stream1dArray<std::uint64_t, std::uint64_t> stream(
566 &dhandle,
567 len,
568 &hhandle,
569 hlen,
570 [&]{
571 ritsuko::cvls::Stream1dArrayOptions opt;
572 opt.contiguous_chunk_size = options.buffer_size;
573 return opt;
574 }()
575 );
576 iterate_stream<std::string>(stream, set);
577 }
578
579 } else if (vector_type == "string" || (version.equals(1, 0) && (vector_type == "date" || vector_type == "date-time"))) {
580 StringVector::Format format = StringVector::NONE;
581 if (version.equals(1, 0)) {
582 if (vector_type == "date") {
583 format = StringVector::DATE;
584 } else if (vector_type == "date-time") {
585 format = StringVector::DATETIME;
586 }
587
588 } else if (handle.exists("format")) {
589 who_failed = FORMAT;
590 auto fhandle = handle.openDataSet("format");
591 if (fhandle.getSpace().getSimpleExtentNdims() != 0) {
592 throw std::runtime_error("expected a scalar dataset");
593 }
594 if (!ritsuko::hdf5::is_utf8_string(fhandle)) {
595 throw std::runtime_error("expected a datatype that can be represented by a UTF-8 encoded string");
596 }
597
598 auto x = ritsuko::hdf5::read_scalar_string(fhandle);
599 if (x == "date") {
600 format = StringVector::DATE;
601 } else if (x == "date-time") {
602 format = StringVector::DATETIME;
603 } else {
604 throw std::runtime_error("unsupported format '" + x + "'");
605 }
606 who_failed = DATA;
607 }
608
609 auto sptr = Provisioner_::new_String(sanisizer::cast<std::size_t>(len), named, is_scalar, format);
610 output.reset(sptr);
611 if (format == StringVector::NONE) {
612 parse_string_like(
613 dhandle,
614 sptr,
615 is_scalar,
616 [](const std::string&) -> void {},
617 options
618 );
619
620 } else if (format == StringVector::DATE) {
621 parse_string_like(
622 dhandle,
623 sptr,
624 is_scalar,
625 [&](const std::string& x) -> void {
626 if (!ritsuko::is_date(x.c_str(), x.size())) {
627 throw std::runtime_error("dates should follow YYYY-MM-DD formatting");
628 }
629 },
630 options
631 );
632
633 } else if (format == StringVector::DATETIME) {
634 parse_string_like(
635 dhandle,
636 sptr,
637 is_scalar,
638 [&](const std::string& x) -> void {
639 if (!ritsuko::is_rfc3339(x.c_str(), x.size())) {
640 throw std::runtime_error("date-times should follow the Internet Date/Time format");
641 }
642 },
643 options
644 );
645 }
646
647 } else if (vector_type == "number") {
648 auto dptr = Provisioner_::new_Number(sanisizer::cast<std::size_t>(len), named, is_scalar);
649 output.reset(dptr);
650 parse_numbers(
651 dhandle,
652 dptr,
653 is_scalar,
654 [](double) -> void {},
655 version,
656 options
657 );
658
659 } else {
660 throw std::runtime_error("unknown vector type '" + vector_type + "'");
661 }
662 } catch (...) {
663 std::string desc;
664 switch (who_failed) {
665 case DATA: desc = "data"; break;
666 case ORDERED: desc = "ordered"; break;
667 case LEVELS: desc = "levels"; break;
668 case HEAP: desc = "heap"; break;
669 case FORMAT: desc = "format"; break;
670 };
671 std::throw_with_nested(std::runtime_error("failed to read '" + desc + "'"));
672 }
673
674 if (named) {
675 auto vptr = static_cast<Vector*>(output.get());
676 try {
677 auto nhandle = handle.openDataSet("names");
678 parse_names(nhandle, vptr, options);
679 } catch (...) {
680 std::throw_with_nested(std::runtime_error("failed to read 'names'"));
681 }
682 }
683
684 } else if (object_type == "nothing") {
685 output.reset(Provisioner_::new_Nothing());
686
687 } else if (object_type == "external") {
688 try {
689 auto ihandle = handle.openDataSet("index");
690 if (ritsuko::hdf5::exceeds_integer_limit(ihandle, 32, true)) {
691 throw std::runtime_error("external index at 'index' cannot be represented by a 32-bit signed integer");
692 }
693
694 if (ihandle.getSpace().getSimpleExtentNdims() != 0) {
695 throw std::runtime_error("expected scalar dataset at 'index'");
696 }
697
698 std::int32_t idx;
699 ihandle.read(&idx, H5::PredType::NATIVE_INT32);
700 if (idx < 0) {
701 throw std::runtime_error("external index at 'index' should be non-negative");
702 } else if (sanisizer::is_greater_than_or_equal(idx, ext.size())) {
703 throw std::runtime_error("external index at 'index' is out of range");
704 }
705
706 output.reset(Provisioner_::new_External(ext.get(idx)));
707 } catch (...) {
708 std::throw_with_nested(std::runtime_error("failed to read 'index'"));
709 }
710
711 } else {
712 throw std::runtime_error("unknown uzuki2 object type '" + object_type + "'");
713 }
714
715 return output;
716}
771template<class Provisioner_, class Externals_>
772ParsedList parse(const H5::Group& group, Externals_ ext, const Options& options) {
773 Version version;
774 if (group.attrExists("uzuki_version")) {
775 try {
776 auto vhandle = group.openAttribute("uzuki_version");
777 auto ver_str = safe_read_scalar_string_attribute(vhandle);
778 auto vraw = ritsuko::parse_version_string(ver_str.c_str(), ver_str.size(), /* skip_patch = */ true);
779 version.major = vraw.major;
780 version.minor = vraw.minor;
781 } catch (...) {
782 std::throw_with_nested(std::runtime_error("failed to read the 'uzuki_version' attribute"));
783 }
784 }
785
786 ExternalTracker etrack(std::move(ext));
787 auto ptr = parse_inner<Provisioner_>(group, etrack, version, options);
788
789 if (options.strict_list && ptr->type() != LIST) {
790 throw std::runtime_error("top-level object should represent an R list");
791 }
792 etrack.validate();
793
794 return ParsedList(std::move(ptr), std::move(version));
795}
796
816template<class Provisioner_, class Externals_>
817ParsedList parse(const std::string& file, const std::string& name, Externals_ ext, Options options = Options()) {
818 try {
819 H5::H5File fhandle(file, H5F_ACC_RDONLY);
820 return parse<Provisioner_>(fhandle.openGroup(name), std::move(ext), options);
821 } catch (...) {
822 std::throw_with_nested(std::runtime_error("failed to parse '" + name + "' in '" + file + "'"));
823 }
824}
825
838inline void validate(const H5::Group& group, int num_external, const Options& options) {
839 parse<DummyProvisioner>(group, DummyExternals(num_external), options);
840}
841
855inline void validate(const std::string& file, const std::string& name, int num_external, const Options& options) {
856 parse<DummyProvisioner>(file, name, DummyExternals(num_external), options);
857}
858
859}
860
861}
862
863#endif
Dummy classes for parsing without storing the results.
Class to hold the parsed list.
Dummy class satisfying the Externals_ interface of hdf5::parse().
Definition Dummy.hpp:131
Format
Definition interfaces.hpp:159
Defines the interfaces to use in HDF5 parsing.
Container_ create(Value_ x, Args_ &&... args)
constexpr bool is_equal(Left_ left, Right_ right)
constexpr bool is_greater_than_or_equal(Left_ left, Right_ right)
constexpr Dest_ cap(Value_ x)
constexpr Dest_ cast(Value_ x)
ParsedList parse(const H5::Group &group, Externals_ ext, const Options &options)
Definition parse_hdf5.hpp:772
void validate(const H5::Group &group, int num_external, const Options &options)
Definition parse_hdf5.hpp:838
Parse an R list from a HDF5 or JSON file.
Definition parse_json.hpp:30
Results of parsing a list from file.
Definition ParsedList.hpp:19
Options for HDF5 file parsing.
Definition parse_hdf5.hpp:44
hsize_t buffer_size
Definition parse_hdf5.hpp:49
bool strict_list
Definition parse_hdf5.hpp:54