45inline const std::vector<std::shared_ptr<millijson::Base> >& extract_array(
const std::unordered_map<std::string, std::shared_ptr<millijson::Base> >& properties,
const std::string& name) {
46 auto it = properties.find(name);
47 if (it == properties.end()) {
48 throw std::runtime_error(
"no such property");
51 const auto& ptr = it->second;
52 if (ptr->type() != millijson::ARRAY) {
53 throw std::runtime_error(
"expected an array");
56 return static_cast<const millijson::Array*
>(ptr.get())->value();
59inline const millijson::Array* extract_array_or_null(
const std::unordered_map<std::string, std::shared_ptr<millijson::Base> >& properties,
const std::string& name) {
60 auto it = properties.find(name);
61 if (it == properties.end()) {
65 const auto ptr = it->second;
66 if (ptr->type() != millijson::ARRAY) {
67 throw std::runtime_error(
"expected an array");
70 return static_cast<const millijson::Array*
>(ptr.get());
73template<
class Destination_>
74void fill_names(
const millijson::Array* names_ptr, Destination_* dest) {
75 const auto& names = names_ptr->value();
76 if (names.size() != dest->size()) {
77 throw std::runtime_error(
"unexpected number of names");
80 const auto nnames = names.size();
81 for (I<
decltype(nnames)> i = 0; i < nnames; ++i) {
82 if (names[i]->type() != millijson::STRING) {
83 throw std::runtime_error(
"expected a string at element " + ritsuko::itoa(i));
85 dest->set_name(i,
static_cast<const millijson::String*
>(names[i].get())->value());
89template<
class Function_>
90auto process_array_or_scalar_values(
const std::unordered_map<std::string, std::shared_ptr<millijson::Base> >& properties, Function_ fun) {
91 const millijson::Array* names_ptr;
93 names_ptr = extract_array_or_null(properties,
"names");
95 std::throw_with_nested(std::runtime_error(
"failed to read 'names'"));
100 auto vIt = properties.find(
"values");
101 if (vIt == properties.end()) {
102 throw std::runtime_error(
"no such property");
105 const auto& values_ptr = vIt->second;
106 if (values_ptr->type() == millijson::ARRAY) {
107 return fun(
static_cast<const millijson::Array*
>(values_ptr.get())->value(), names_ptr != NULL,
false);
109 std::vector<std::shared_ptr<millijson::Base> > temp { values_ptr };
110 return fun(temp, names_ptr != NULL,
true);
113 std::throw_with_nested(std::runtime_error(
"failed to read 'values'"));
117 if (names_ptr != NULL) {
119 fill_names(names_ptr, out_ptr);
121 std::throw_with_nested(std::runtime_error(
"failed to parse 'names'"));
127template<
class Destination_,
class Function_>
128void extract_integers(
const std::vector<std::shared_ptr<millijson::Base> >& values, Destination_* dest, Function_ check,
const Version& version) {
129 const auto n = values.size();
130 for (I<
decltype(n)> i = 0; i < n; ++i) {
132 if (values[i]->type() == millijson::NOTHING) {
133 dest->set_missing(i);
137 if (values[i]->type() != millijson::NUMBER) {
138 throw std::runtime_error(
"expected a number");
141 auto val =
static_cast<const millijson::Number*
>(values[i].get())->value();
142 if (val != std::floor(val)) {
143 throw std::runtime_error(
"expected an integer");
147 constexpr double upper = std::numeric_limits<std::int32_t>::max();
148 constexpr double lower = std::numeric_limits<std::int32_t>::min();
149 if (val < lower || val > upper) {
150 throw std::runtime_error(
"value cannot be represented by a 32-bit signed integer");
153 const std::int32_t ival = val;
154 if (version.equals(1, 0) && ival == -2147483648) {
155 dest->set_missing(i);
162 std::throw_with_nested(std::runtime_error(
"failed to process element " + ritsuko::itoa(i)));
167template<
class Destination_,
class Function_>
168void extract_strings(
const std::vector<std::shared_ptr<millijson::Base> >& values, Destination_* dest, Function_ check) {
169 const auto n = values.size();
170 for (I<
decltype(n)> i = 0; i < n; ++i) {
172 if (values[i]->type() == millijson::NOTHING) {
173 dest->set_missing(i);
177 if (values[i]->type() != millijson::STRING) {
178 throw std::runtime_error(
"expected a string");
181 const auto& str =
static_cast<const millijson::String*
>(values[i].get())->value();
185 std::throw_with_nested(std::runtime_error(
"failed to process element " + ritsuko::itoa(i)));
190template<
class Provisioner_,
class Externals_>
191std::shared_ptr<Base> parse_object(
const millijson::Base* contents, Externals_& ext,
const Version& version) {
192 if (contents->type() != millijson::OBJECT) {
193 throw std::runtime_error(
"each R object should be represented by a JSON object");
195 const auto& map =
static_cast<const millijson::Object*
>(contents)->value();
197 const auto& type = [&]() ->
const std::string& {
199 auto tIt = map.find(
"type");
200 if (tIt == map.end()) {
201 throw std::runtime_error(
"no such property");
204 const auto& type_ptr = tIt->second;
205 if (type_ptr->type() != millijson::STRING) {
206 throw std::runtime_error(
"expected a string");
208 return static_cast<const millijson::String*
>(type_ptr.get())->value();
210 std::throw_with_nested(std::runtime_error(
"failed to read 'type'"));
214 std::shared_ptr<Base> output;
215 if (type ==
"nothing") {
216 output.reset(Provisioner_::new_Nothing());
218 }
else if (type ==
"external") {
220 auto iIt = map.find(
"index");
221 if (iIt == map.end()) {
222 throw std::runtime_error(
"no such property");
224 const auto& index_ptr = iIt->second;
225 if (index_ptr->type() != millijson::NUMBER) {
226 throw std::runtime_error(
"expected a number");
229 const auto flt_index =
static_cast<const millijson::Number*
>(index_ptr.get())->value();
230 if (flt_index != std::floor(flt_index)) {
231 throw std::runtime_error(
"expected an integer");
232 }
else if (flt_index < 0) {
233 throw std::runtime_error(
"expected a non-negative integer");
240 throw std::runtime_error(
"value should fit in a 32-bit signed integer");
243 throw std::runtime_error(
"external index out of range");
245 output.reset(Provisioner_::new_External(ext.get(index)));
247 std::throw_with_nested(std::runtime_error(
"failed to read 'index'"));
250 }
else if (type ==
"integer") {
251 process_array_or_scalar_values(
253 [&](
const auto& vals,
bool named,
bool scalar) ->
auto {
256 extract_integers(vals, ptr, [](std::int32_t) ->
void {}, version);
261 }
else if (type ==
"factor" || (version.equals(1, 0) && type ==
"ordered")) {
262 bool ordered =
false;
263 if (type ==
"ordered") {
266 auto oIt = map.find(
"ordered");
267 if (oIt != map.end()) {
268 if (oIt->second->type() != millijson::BOOLEAN) {
269 throw std::runtime_error(
"expected a boolean at 'ordered'");
271 ordered =
static_cast<const millijson::Boolean*
>((oIt->second).get())->value();
275 bool level_failed =
true;
277 const std::string level_field =
"levels";
278 const auto& lvals = extract_array(map, level_field);
279 const auto nlevels = lvals.size();
281 level_failed =
false;
282 auto fptr = process_array_or_scalar_values(
284 [&](
const auto& vals,
bool named,
bool scalar) ->
auto {
290 [&](std::int32_t x) ->
void {
292 throw std::runtime_error(
"factor code should be non-negative");
294 throw std::runtime_error(
"factor code should be less than the number of levels");
304 std::unordered_set<std::string> existing;
305 for (I<
decltype(nlevels)> l = 0; l < nlevels; ++l) {
307 if (lvals[l]->type() != millijson::STRING) {
308 throw std::runtime_error(
"expected a string");
311 const auto& level =
static_cast<const millijson::String*
>(lvals[l].get())->value();
312 if (existing.find(level) != existing.end()) {
313 throw std::runtime_error(
"detected duplicate level '" + level +
"'");
315 fptr->set_level(l, level);
316 existing.insert(level);
318 std::throw_with_nested(std::runtime_error(
"failed to read element " + ritsuko::itoa(l)));
323 std::throw_with_nested(std::runtime_error(
"failed to read 'levels'"));
329 }
else if (type ==
"boolean") {
330 process_array_or_scalar_values(
332 [&](
const auto& vals,
bool named,
bool scalar) ->
auto {
333 const auto n = vals.size();
337 for (I<
decltype(n)> i = 0; i < n; ++i) {
339 if (vals[i]->type() == millijson::NOTHING) {
344 if (vals[i]->type() != millijson::BOOLEAN) {
345 throw std::runtime_error(
"expected a boolean");
347 ptr->set(i,
static_cast<const millijson::Boolean*
>(vals[i].get())->value());
349 std::throw_with_nested(std::runtime_error(
"failed to read element " + ritsuko::itoa(i)));
357 }
else if (type ==
"number") {
358 process_array_or_scalar_values(
360 [&](
const auto& vals,
bool named,
bool scalar) ->
auto {
361 const auto n = vals.size();
365 for (I<
decltype(n)> i = 0; i < n; ++i) {
367 if (vals[i]->type() == millijson::NOTHING) {
372 if (vals[i]->type() == millijson::NUMBER) {
373 ptr->set(i,
static_cast<const millijson::Number*
>(vals[i].get())->value());
374 }
else if (vals[i]->type() == millijson::STRING) {
375 auto str =
static_cast<const millijson::String*
>(vals[i].get())->value();
377 ptr->set(i, std::numeric_limits<double>::quiet_NaN());
378 }
else if (str ==
"Inf") {
379 ptr->set(i, std::numeric_limits<double>::infinity());
380 }
else if (str ==
"-Inf") {
381 ptr->set(i, -std::numeric_limits<double>::infinity());
383 throw std::runtime_error(
"unsupported string '" + str +
"'");
386 throw std::runtime_error(
"expected a number");
389 std::throw_with_nested(std::runtime_error(
"failed to read element " + ritsuko::itoa(i)));
397 }
else if (type ==
"string" || (version.equals(1, 0) && (type ==
"date" || type ==
"date-time"))) {
399 if (version.equals(1, 0)) {
400 if (type ==
"date") {
401 format = StringVector::DATE;
402 }
else if (type ==
"date-time") {
403 format = StringVector::DATETIME;
406 auto fIt = map.find(
"format");
407 if (fIt != map.end()) {
408 if (fIt->second->type() != millijson::STRING) {
409 throw std::runtime_error(
"expected a string at 'format'");
411 auto fptr =
static_cast<const millijson::String*
>(fIt->second.get());
412 if (fptr->value() ==
"date") {
413 format = StringVector::DATE;
414 }
else if (fptr->value() ==
"date-time") {
415 format = StringVector::DATETIME;
417 throw std::runtime_error(
"unsupported format '" + fptr->value() +
"' at 'format'");
422 process_array_or_scalar_values(
424 [&](
const auto& vals,
bool named,
bool scalar) ->
auto {
428 if (format == StringVector::NONE) {
429 extract_strings(vals, ptr, [](
const std::string&) ->
void {});
430 }
else if (format == StringVector::DATE) {
434 [&](
const std::string& x) ->
void {
435 if (!ritsuko::is_date(x.c_str(), x.size())) {
436 throw std::runtime_error(
"dates should follow YYYY-MM-DD formatting");
440 }
else if (format == StringVector::DATETIME) {
444 [&](
const std::string& x) ->
void {
445 if (!ritsuko::is_rfc3339(x.c_str(), x.size())) {
446 throw std::runtime_error(
"date-times should follow the Internet Date/Time format");
456 }
else if (type ==
"list") {
457 const millijson::Array* names_ptr;
459 names_ptr = extract_array_or_null(map,
"names");
461 std::throw_with_nested(std::runtime_error(
"failed to read 'names'"));
466 const std::string value_field =
"values";
467 const auto& vals = extract_array(map, value_field);
469 const auto n = vals.size();
473 for (I<
decltype(n)> i = 0; i < n; ++i) {
475 lptr->
set(i, parse_object<Provisioner_>(vals[i].get(), ext, version));
477 std::throw_with_nested(std::runtime_error(
"failed to read element " + ritsuko::itoa(i)));
481 std::throw_with_nested(std::runtime_error(
"failed to read 'values'"));
484 if (names_ptr != NULL) {
486 fill_names(names_ptr, lptr);
488 std::throw_with_nested(std::runtime_error(
"failed to read 'names'"));
493 throw std::runtime_error(
"unknown object type '" + type +
"' in 'type'");
544template<
class Provisioner_,
class Reader_,
class Externals_>
546 millijson::ParseOptions popt;
549 auto contents = millijson::parse(reader, popt);
552 if (contents->type() == millijson::OBJECT) {
553 const auto& map =
static_cast<const millijson::Object*
>(contents.get())->value();
554 auto vIt = map.find(
"version");
555 if (vIt != map.end()) {
557 if (vIt->second->type() != millijson::STRING) {
558 throw std::runtime_error(
"expected a string");
560 const auto& vstr =
static_cast<const millijson::String*
>(vIt->second.get())->value();
561 auto vraw = ritsuko::parse_version_string(vstr.c_str(), vstr.size(),
true);
562 version.major = vraw.major;
563 version.minor = vraw.minor;
565 std::throw_with_nested(std::runtime_error(
"failed to read 'version'"));
570 ExternalTracker etrack(std::move(ext));
571 auto output = parse_object<Provisioner_>(contents.get(), etrack, version);
573 if (options.
strict_list && output->type() != LIST) {
574 throw std::runtime_error(
"top-level object should represent an R list");
578 return ParsedList(std::move(output), std::move(version));
599template<
class Provisioner_,
class Externals_>
601 std::unique_ptr<byteme::Reader> ptr;
629template<
class Provisioner_,
class Externals_>
631 std::unique_ptr<byteme::Reader> ptr;