MARLEY (Model of Argon Reaction Low Energy Yields) v2.0.0
A Monte Carlo event generator for tens-of-MeV neutrino interactions
Loading...
Searching...
No Matches
JSON.hh
1
4//
5// This file is part of MARLEY (Model of Argon Reaction Low Energy Yields)
6//
7// MARLEY is free software: you can redistribute it and/or modify it under the
8// terms of version 3 of the GNU General Public License as published by the
9// Free Software Foundation.
10//
11// For the full text of the license please see COPYING or
12// visit http://opensource.org/licenses/GPL-3.0
13//
14// Please respect the MCnet academic usage guidelines. See GUIDELINES
15// or visit https://www.montecarlonet.org/GUIDELINES for details.
16
17// Based on https://github.com/nbsdx/SimpleJSON
18
19#pragma once
20
21// standard library includes
22#include <cctype>
23#include <cmath>
24#include <cstdint>
25#include <deque>
26#include <initializer_list>
27#include <iostream>
28#include <istream>
29#include <ostream>
30#include <limits>
31#include <sstream>
32#include <map>
33#include <memory>
34#include <string>
35#include <type_traits>
36
37// MARLEY includes
38#include "marley/marley_utils.hh"
39#include "marley/Error.hh"
40#include "marley/FileManager.hh"
41#include "marley/Logger.hh"
42
43namespace marley {
44
45 namespace {
46 std::string json_escape( const std::string& str ) {
47 std::string output;
48 for( unsigned i = 0; i < str.length(); ++i )
49 switch( str[i] ) {
50 case '\"': output += "\\\""; break;
51 case '\\': output += "\\\\"; break;
52 case '\b': output += "\\b"; break;
53 case '\f': output += "\\f"; break;
54 case '\n': output += "\\n"; break;
55 case '\r': output += "\\r"; break;
56 case '\t': output += "\\t"; break;
57 default : output += str[i]; break;
58 }
59 return output;
60 }
61 }
62
63 // Forward-declare the StreamReader class defined below
64 class StreamReader;
65
66 class JSON
67 {
68 union Data {
69 Data(double d) : float_(d) {}
70 Data(long l) : integer_(l) {}
71 Data(bool b) : boolean_(b) {}
72 Data(const std::string& s) : string_(new std::string(s)) {}
73 Data() : integer_(0) {}
74
75 std::deque<JSON>* list_;
76 std::map<std::string, JSON>* map_;
77 std::string* string_;
78 double float_;
79 long integer_;
80 bool boolean_;
81 } data_;
82
83 public:
84 enum class DataType {
85 Null,
86 Object,
87 Array,
88 String,
89 Floating,
90 Integral,
91 Boolean
92 };
93
94 template <typename Container> class JSONWrapper {
95
96 private:
97 Container* object;
98
99 public:
100 JSONWrapper(Container* val) : object(val) {}
101 JSONWrapper(std::nullptr_t) : object(nullptr) {}
102
103 typename Container::iterator begin() {
104 return object ? object->begin() : typename Container::iterator();
105 }
106 typename Container::iterator end() {
107 return object ? object->end() : typename Container::iterator();
108 }
109 typename Container::const_iterator begin() const {
110 return object ? object->begin() : typename Container::iterator();
111 }
112 typename Container::const_iterator end() const {
113 return object ? object->end() : typename Container::iterator();
114 }
115 };
116
117 template <typename Container>
118 class JSONConstWrapper {
119
120 private:
121 const Container* object;
122
123 public:
124 JSONConstWrapper(const Container* val) : object(val) {}
125 JSONConstWrapper(std::nullptr_t) : object(nullptr) {}
126
127 typename Container::const_iterator begin() const
128 { return object ? object->begin()
129 : typename Container::const_iterator(); }
130 typename Container::const_iterator end() const
131 { return object ? object->end()
132 : typename Container::const_iterator(); }
133 };
134
135 JSON() : data_(), type_(DataType::Null) {}
136
137 JSON(std::initializer_list<JSON> list) : JSON()
138 {
139 set_type(DataType::Object);
140 for(auto i = list.begin(), e = list.end(); i != e; ++i, ++i)
141 operator[](i->to_string()) = *std::next(i);
142 }
143
144 JSON(JSON&& other) : data_(other.data_), type_(other.type_)
145 { other.type_ = DataType::Null; other.data_.map_ = nullptr; }
146
147 JSON& operator=(JSON&& other) {
148 data_ = other.data_;
149 type_ = other.type_;
150 other.data_.map_ = nullptr;
151 other.type_ = DataType::Null;
152 return *this;
153 }
154
155 JSON(const JSON& other) {
156 switch(other.type_) {
157 case DataType::Object:
158 data_.map_ = new std::map<std::string,JSON>(
159 other.data_.map_->begin(), other.data_.map_->end());
160 break;
161 case DataType::Array:
162 data_.list_ = new std::deque<JSON>(other.data_.list_->begin(),
163 other.data_.list_->end());
164 break;
165 case DataType::String:
166 data_.string_ = new std::string(*other.data_.string_);
167 break;
168 default:
169 data_ = other.data_;
170 }
171 type_ = other.type_;
172 }
173
174 JSON& operator=(const JSON& other) {
175 switch(other.type_) {
176 case DataType::Object:
177 data_.map_ = new std::map<std::string,JSON>(
178 other.data_.map_->begin(), other.data_.map_->end());
179 break;
180 case DataType::Array:
181 data_.list_ = new std::deque<JSON>( other.data_.list_->begin(),
182 other.data_.list_->end());
183 break;
184 case DataType::String:
185 data_.string_ = new std::string(*other.data_.string_);
186 break;
187 default:
188 data_ = other.data_;
189 }
190 type_ = other.type_;
191 return *this;
192 }
193
194 ~JSON() {
195 switch(type_) {
196 case DataType::Array:
197 delete data_.list_;
198 break;
199 case DataType::Object:
200 delete data_.map_;
201 break;
202 case DataType::String:
203 delete data_.string_;
204 break;
205 default:;
206 }
207 }
208
209 template <typename T> JSON(T b,
210 typename std::enable_if<std::is_same<T,bool>::value>::type* = 0)
211 : data_(b), type_(DataType::Boolean) {}
212
213 template <typename T> JSON(T i,
214 typename std::enable_if<std::is_integral<T>::value
215 && !std::is_same<T,bool>::value>::type* = 0)
216 : data_(static_cast<long>(i)), type_(DataType::Integral) {}
217
218 template <typename T> JSON(T f,
219 typename std::enable_if<std::is_floating_point<T>::value>::type* = 0)
220 : data_(static_cast<double>(f)), type_(DataType::Floating) {}
221
222 explicit JSON(const std::string& s)
223 : data_(s), type_(DataType::String) {}
224
225 //template <typename T> JSON(T s,
226 // typename std::enable_if<std::is_convertible<T,
227 // std::string>::value>::type* = 0) : data_(std::string(s)),
228 // type_(DataType::String) {}
229
230 JSON(std::nullptr_t) : data_(), type_(DataType::Null) {}
231
232 static inline JSON make(DataType type) {
233 JSON ret;
234 ret.set_type(type);
235 return ret;
236 }
237
238 static inline JSON array() {
239 return JSON::make(JSON::DataType::Array);
240 }
241
242 template <typename... T>
243 static JSON array( T... args )
244 {
245 JSON arr = JSON::make(JSON::DataType::Array);
246 arr.append(args...);
247 return arr;
248 }
249
250 static inline JSON object() {
251 return JSON::make(JSON::DataType::Object);
252 }
253
254 static inline JSON load(const std::string& s);
255 static inline JSON load(std::istream& is);
256 static inline JSON load(StreamReader& reader);
257 static inline JSON load_file(const std::string& s);
258
259 template <typename T> void append(T arg) {
260 set_type(DataType::Array);
261 data_.list_->emplace_back(arg);
262 }
263
264 template <typename T, typename... U> void append(T arg, U... args) {
265 append(arg); append(args...);
266 }
267
268 template <typename T>
269 typename std::enable_if<std::is_same<T,bool>::value, JSON&>::type
270 operator=(T b)
271 {
272 set_type(DataType::Boolean);
273 data_.boolean_ = b;
274 return *this;
275 }
276
277 template <typename T> typename std::enable_if<std::is_integral<T>::value
278 && !std::is_same<T,bool>::value, JSON&>::type operator=(T i)
279 {
280 set_type( DataType::Integral );
281 data_.integer_ = i;
282 return *this;
283 }
284
285 template <typename T>
286 typename std::enable_if<std::is_floating_point<T>::value, JSON&>::type
287 operator=(T f)
288 {
289 set_type(DataType::Floating);
290 data_.float_ = f;
291 return *this;
292 }
293
294 template <typename T> typename std::enable_if<std::is_convertible<T,
295 std::string>::value, JSON&>::type operator=(T s)
296 {
297 set_type(DataType::String);
298 *data_.string_ = std::string(s);
299 return *this;
300 }
301
302 JSON& operator[](const std::string& key) {
303 set_type(DataType::Object);
304 return data_.map_->operator[](key);
305 }
306
307 JSON& operator[](unsigned index) {
308 set_type(DataType::Array);
309 if (index >= data_.list_->size()) data_.list_->resize(index + 1);
310 return data_.list_->operator[](index);
311 }
312
313 JSON& at(const std::string& key) {
314 return operator[](key);
315 }
316
317 const JSON& at(const std::string &key) const {
318 return data_.map_->at(key);
319 }
320
321 JSON& at(unsigned index) {
322 return operator[](index);
323 }
324
325 const JSON& at(unsigned index) const {
326 return data_.list_->at(index);
327 }
328
329 int length() const {
330 if (type_ == DataType::Array) return data_.list_->size();
331 else return -1;
332 }
333
334 bool has_key(const std::string& key) const {
335 if (type_ == DataType::Object)
336 return data_.map_->find( key ) != data_.map_->end();
337 else return false;
338 }
339
340 int size() const {
341 if (type_ == DataType::Object)
342 return data_.map_->size();
343 else if (type_ == DataType::Array)
344 return data_.list_->size();
345 else
346 return -1;
347 }
348
349 inline DataType type() const { return type_; }
350
352 inline bool is_null() const { return type_ == DataType::Null; }
353 inline bool is_object() const { return type_ == DataType::Object; }
354 inline bool is_array() const { return type_ == DataType::Array; }
355 inline bool is_string() const { return type_ == DataType::String; }
356 inline bool is_float() const { return type_ == DataType::Floating; }
357 inline bool is_integer() const { return type_ == DataType::Integral; }
358 inline bool is_bool() const { return type_ == DataType::Boolean; }
359
360 std::string to_string() const {
361 bool b;
362 return to_string(b);
363 }
364
365 std::string to_string(bool& ok) const {
366 ok = (type_ == DataType::String);
367 return ok ? json_escape(*data_.string_) : std::string("");
368 }
369
370 std::string to_string_or_throw() const {
371 bool ok;
372 std::string result = to_string(ok);
373 if (!ok) throw marley::Error("Failed to convert JSON value to string");
374 return result;
375 }
376
377 double to_double() const {
378 bool b;
379 return to_double(b);
380 }
381
382 double to_double(bool& ok) const {
383 ok = (type_ == DataType::Floating);
384 if (ok) return data_.float_;
385 ok = (type_ == DataType::Integral);
386 if (ok) return data_.integer_;
387 return 0.;
388 }
389
390 double to_double_or_throw() const {
391 bool ok;
392 double result = to_double(ok);
393 if (!ok) throw marley::Error("Failed to convert JSON value '"
394 + to_string() + "' to double");
395 return result;
396 }
397
398 long to_long() const {
399 bool b;
400 return to_long( b );
401 }
402
403 long to_long(bool& ok) const {
404 ok = (type_ == DataType::Integral);
405 return ok ? data_.integer_ : 0;
406 }
407
408 long to_long_or_throw() const {
409 bool ok;
410 double result = to_long(ok);
411 if (!ok) throw marley::Error("Failed to convert JSON value '"
412 + to_string() + "' to long");
413 return result;
414 }
415
416 bool to_bool() const {
417 bool b;
418 return to_bool( b );
419 }
420
421 bool to_bool(bool& ok) const {
422 ok = (type_ == DataType::Boolean);
423 return ok ? data_.boolean_ : false;
424 }
425
426 bool to_bool_or_throw() const {
427 bool ok;
428 double result = to_bool(ok);
429 if (!ok) throw marley::Error("Failed to convert JSON value '"
430 + to_string() + "' to bool");
431 return result;
432 }
433
435 if (type_ == DataType::Object)
436 return JSONWrapper<std::map<std::string,JSON>>(data_.map_);
437 else return JSONWrapper<std::map<std::string,JSON>>(nullptr);
438 }
439
440 JSONWrapper<std::deque<JSON> > array_range() {
441 if (type_ == DataType::Array)
442 return JSONWrapper<std::deque<JSON>>(data_.list_);
443 else return JSONWrapper<std::deque<JSON>>(nullptr);
444 }
445
446 JSONConstWrapper<std::map<std::string,JSON> > object_range() const {
447 if (type_ == DataType::Object)
450 }
451
452
453 JSONConstWrapper<std::deque<JSON>> array_range() const {
454 if ( type_ == DataType::Array )
455 return JSONConstWrapper<std::deque<JSON>>(data_.list_);
456 else return JSONConstWrapper<std::deque<JSON>>(nullptr);
457 }
458
459 // Portions of the serialization functions (dump_string, print)
460 // are based on techniques used in the JSON for Modern C++
461 // library by Niels Lohmann (https://github.com/nlohmann/json).
462 std::string dump_string(const int indent_step = -1) const {
463 std::stringstream out;
464 // Enable pretty-printing if the user specified a nonnegative
465 // indent_step value
466 if (indent_step >= 0)
467 print(out, static_cast<unsigned int>(indent_step), true);
468 // Otherwise, print the JSON object in the most compact form possible
469 else print(out, 0, false);
470
471 // Return the completed JSON string
472 return out.str();
473 }
474
475 // Implementation of serialization to text. Used by the public
476 // dump_string() method.
477 void print(std::ostream& out, const unsigned int indent_step,
478 bool pretty, const unsigned int current_indent = 0) const
479 {
480 // Use max_digits10 for outputting double-precision floating-point
481 // numbers. This ensures that repeated input/output via JSON will
482 // not result in any loss of precision. For more information, please
483 // see http://tinyurl.com/p8wyhnn
484 static std::ostringstream out_float;
485 static bool set_precision = false;
486 if (!set_precision) {
487 out_float.precision(std::numeric_limits<double>::max_digits10);
488 set_precision = true;
489 }
490
491 unsigned int indent = current_indent;
492
493 switch( type_ ) {
494 case DataType::Null:
495 out << "null";
496 return;
497 case DataType::Object: {
498 out << '{';
499 if (pretty) {
500 indent += indent_step;
501 out << '\n';
502 }
503 bool skip = true;
504 for( auto &p : *data_.map_ ) {
505 if ( !skip ) {
506 out << ',';
507 if (pretty) out << '\n';
508 }
509
510 out << std::string(indent, ' ') << '\"'
511 << json_escape( p.first ) << '\"';
512
513 if (pretty) out << " : ";
514 else out << ':';
515
516 p.second.print( out, indent_step, pretty, indent );
517 skip = false;
518 }
519 if (pretty) {
520 indent -= indent_step;
521 out << '\n';
522 }
523 out << std::string(indent, ' ') + '}';
524 return;
525 }
526 case DataType::Array: {
527 out << '[';
528 if (pretty) {
529 indent += indent_step;
530 out << '\n';
531 }
532 bool skip = true;
533 for( auto &p : *data_.list_ ) {
534 if ( !skip ) {
535 out << ',';
536 if (pretty) out << '\n';
537 }
538 out << std::string(indent, ' ');
539 p.print( out, indent_step, pretty, indent );
540 skip = false;
541 }
542 if (pretty) {
543 indent -= indent_step;
544 out << '\n';
545 }
546 out << std::string(indent, ' ') << ']';
547 return;
548 }
549 case DataType::String:
550 out << '\"' + json_escape( *data_.string_ ) + '\"';
551 return;
552 case DataType::Floating:
553 // Clear any previous contents of the stringstream
554 out_float.str("");
555 out_float.clear();
556 // Fill it with the new floating-point number
557 out_float << data_.float_;
558 // Output the resulting string to the stream
559 out << out_float.str();
560 return;
561 case DataType::Integral:
562 out << data_.integer_;
563 return;
564 case DataType::Boolean:
565 out << (data_.boolean_ ? "true" : "false");
566 return;
567 default:
568 break;
569 }
570
571 return;
572 }
573
574 void check_if_object( const std::string& key ) const {
575 if ( type_ != DataType::Object ) throw marley::Error( "Attempted"
576 " to retrieve a value for the key '" + key + "' from a JSON"
577 " primitive that is not an object" );
578 }
579
580 private:
581
582 void set_type( DataType type ) {
583 if ( type == type_ ) return;
584
585 switch( type_ ) {
586 case DataType::Object:
587 delete data_.map_;
588 break;
589 case DataType::Array:
590 delete data_.list_;
591 break;
592 case DataType::String:
593 delete data_.string_;
594 break;
595 default:;
596 }
597
598 switch( type ) {
599 case DataType::Null:
600 data_.map_ = nullptr;
601 break;
602 case DataType::Object:
603 data_.map_ = new std::map< std::string, JSON >();
604 break;
605 case DataType::Array:
606 data_.list_ = new std::deque< JSON >();
607 break;
608 case DataType::String:
609 data_.string_ = new std::string();
610 break;
611 case DataType::Floating:
612 data_.float_ = 0.;
613 break;
614 case DataType::Integral:
615 data_.integer_ = 0;
616 break;
617 case DataType::Boolean:
618 data_.boolean_ = false;
619 break;
620 }
621
622 type_ = type;
623 }
624
625 public:
626
627 // Attempts to get a floating point number from a JSON object with
628 // a given key. If the attempt fails, throw a marley::Error.
629 double get_double( const std::string& key ) const {
630 check_if_object( key );
631 if ( has_key(key) ) return this->at( key ).to_double_or_throw();
632 else throw marley::Error( "Missing JSON key '" + key + '\'' );
633 return 0.;
634 }
635
636 // Attempts to get a floating point number from a JSON object with
637 // a given key. If the key doesn't exist, use a default value. If a
638 // conversion attempt fails, throw a marley::Error.
639 double get_double( const std::string& key, double default_value ) const {
640 check_if_object( key );
641 if ( !has_key(key) ) return default_value;
642 else return this->at( key ).to_double_or_throw();
643 }
644
645 // Attempts to get an integer from a JSON object with
646 // a given key. If the attempt fails, throw a marley::Error.
647 long get_long( const std::string& key ) const {
648 check_if_object( key );
649 if ( has_key(key) ) return this->at( key ).to_long_or_throw();
650 else throw marley::Error( "Missing JSON key '" + key + '\'' );
651 return 0.;
652 }
653
654 // Attempts to get an integer from a JSON object with
655 // a given key. If the key doesn't exist, use a default value. If a
656 // conversion attempt fails, throw a marley::Error.
657 long get_long( const std::string& key, long default_value ) const {
658 check_if_object( key );
659 if ( !has_key(key) ) return default_value;
660 else return this->at( key ).to_long_or_throw();
661 }
662
663 // Attempts to get a bool from a JSON object with
664 // a given key. If the attempt fails, throw a marley::Error.
665 bool get_bool( const std::string& key ) const {
666 check_if_object( key );
667 if ( has_key(key) ) return this->at( key ).to_bool_or_throw();
668 else throw marley::Error( "Missing JSON key '" + key + '\'' );
669 return 0.;
670 }
671
672 // Attempts to get a bool from a JSON object with
673 // a given key. If the key doesn't exist, use a default value. If a
674 // conversion attempt fails, throw a marley::Error.
675 bool get_bool( const std::string& key, bool default_value ) const {
676 check_if_object( key );
677 if ( !has_key(key) ) return default_value;
678 else return this->at( key ).to_bool_or_throw();
679 }
680
681 // Attempts to get a string from a JSON object with
682 // a given key. If the attempt fails, throw a marley::Error.
683 std::string get_string( const std::string& key ) const {
684 check_if_object( key );
685 if ( has_key(key) ) return this->at( key ).to_string_or_throw();
686 else throw marley::Error( "Missing JSON key '" + key + '\'' );
687 return std::string( "" );
688 }
689
690 // Attempts to get a string from a JSON object with
691 // a given key. If the key doesn't exist, use a default value. If a
692 // conversion attempt fails, throw a marley::Error.
693 std::string get_string( const std::string& key,
694 const std::string& default_value ) const
695 {
696 check_if_object( key );
697 if ( !has_key(key) ) return default_value;
698 else return this->at( key ).to_string_or_throw();
699 }
700
701 // Copies a subobject from a JSON object with a given key. If the attempt
702 // fails, throw a marley::Error, unless the user asks us not to do so.
703 marley::JSON get_object( const std::string& key,
704 bool throw_error = true ) const
705 {
706 check_if_object( key );
707 if ( has_key(key) ) return this->at( key );
708 else if ( throw_error ) throw marley::Error(
709 "Missing JSON key '" + key + '\'' );
710 return JSON::make( JSON::DataType::Object );
711 }
712
713 private:
714
715 DataType type_ = DataType::Null;
716 };
717
718 class StreamReader {
719 public:
720 StreamReader(std::istream& stream, std::string filename = {},
721 StreamReader* parent = nullptr)
722 : stream_(&stream), filename_(std::move(filename)),
723 parent_(parent) {}
724
725 StreamReader(std::unique_ptr<std::istream> stream,
726 std::string filename = {}, StreamReader* parent = nullptr)
727 : stream_(stream.get()), owner_(std::move(stream)),
728 filename_(std::move(filename)), parent_(parent) {}
729
730 char get() {
731 if (has_putback_) {
732 has_putback_ = false;
733 char c = putback_char_;
734 if (c == '\n') { ++line_; col_ = 1; }
735 else { ++col_; }
736 return c;
737 }
738 char c;
739 if (stream_->get(c)) {
740 prev_line_ = line_; prev_col_ = col_; prev_known_ = true;
741 if (c == '\n') { ++line_; col_ = 1; }
742 else { ++col_; }
743 return c;
744 }
745 fail_ = true;
746 return '\0';
747 }
748
749 char peek() {
750 if (has_putback_) return putback_char_;
751 int ch = stream_->peek();
752 if (ch == std::char_traits<char>::eof()) {
753 fail_ = true; return '\0';
754 }
755 return static_cast<char>(ch);
756 }
757
758 void unget(char c) {
759 if (prev_known_) {
760 line_ = prev_line_; col_ = prev_col_;
761 }
762 putback_char_ = c;
763 has_putback_ = true;
764 }
765
766 bool good() const {
767 if (has_putback_) return true;
768 return !fail_ && stream_->good();
769 }
770
771 size_t line() const { return line_; }
772 size_t col() const { return col_; }
773 const std::string& filename() const { return filename_; }
774
775 std::string format_error(const std::string& message) const {
776 std::string result;
777 auto report_col = [&](const StreamReader& r) -> std::string {
778 return std::to_string(r.prev_known_ ? r.prev_line_ : r.line_)
779 + ", column " + std::to_string(r.prev_known_ ? r.prev_col_ : r.col_);
780 };
781 if (!filename_.empty()) {
782 result += "In \"" + filename_ + "\", line " + report_col(*this)
783 + ":\n";
784 }
785 std::string indent = " ";
786 for (const StreamReader* p = parent_; p; p = p->parent_) {
787 result += indent + "(included from \"" + p->filename_
788 + "\", line " + report_col(*p) + ")\n";
789 indent += " ";
790 }
791 result += message;
792 return result;
793 }
794
795 private:
796 std::istream* stream_ = nullptr;
797 std::unique_ptr<std::istream> owner_;
798 std::string filename_;
799 StreamReader* parent_ = nullptr;
800
801 size_t line_ = 1, col_ = 1;
802 size_t prev_line_ = 1, prev_col_ = 1;
803 bool prev_known_ = false;
804
805 char putback_char_ = 0;
806 bool has_putback_ = false;
807 bool fail_ = false;
808 };
809
810 namespace {
811
812 JSON parse_next( StreamReader& );
813
814 void issue_parse_error( char found_char, const std::string& message,
815 StreamReader& reader )
816 {
817 std::string msg( message );
818 if ( !reader.good() ) msg += "end-of-file";
819 else msg += std::string( "\'" ) + found_char + '\'';
820 throw marley::Error( reader.format_error( msg ) );
821 }
822
823 void issue_parse_error( const std::string& found_str,
824 const std::string& message, StreamReader& reader )
825 {
826 std::string msg( message );
827 if ( !reader.good() ) msg += "end-of-file";
828 else msg += '\'' + found_str + '\'';
829 throw marley::Error( reader.format_error( msg ) );
830 }
831
832 // Skips single-line comments // and multi-line comments /* */
833 // These are technically not valid in JSON (the standard doesn't allow
834 // comments), but they are valid in Javascript object literals.
835 void skip_comment( StreamReader& reader, bool is_multiline = false ) {
836 if ( is_multiline ) {
837 char c;
838 while ( c = reader.get(), reader.good() ) {
839 if ( c == '*' && reader.peek() == '/' ) {
840 reader.get();
841 break;
842 }
843 }
844 }
845 else {
846 char c;
847 while ( (c = reader.get(), reader.good()) && c != '\n' ) {}
848 }
849 }
850
851 // Skips whitespace and comments, saving the last character read to
852 // read_char.
853 void skip_ws( StreamReader& reader, char& read_char ) {
854 while ( read_char = reader.get(), std::isspace(read_char) ) continue;
855 if ( read_char == '/' ) {
856 char c = reader.peek();
857 if ( c == '/' || c == '*' ) {
858 read_char = reader.get();
859 skip_comment( reader, c == '*' );
860 return skip_ws( reader, read_char );
861 }
862 }
863 }
864
865 // Removes whitespace and comments from the input stream, putting back
866 // the first non-whitespace and non-comment character it finds.
867 void consume_ws( StreamReader& reader ) {
868 char next;
869 skip_ws( reader, next );
870 reader.unget( next );
871 }
872
873 // Removes whitespace and comments from the input stream, returning the
874 // first non-whitespace and non-comment character it finds.
875 char get_next_char( StreamReader& reader )
876 {
877 char next;
878 skip_ws( reader, next );
879 return next;
880 }
881
882 JSON parse_object( StreamReader& reader ) {
883
884 JSON object = JSON::make( JSON::DataType::Object );
885
886 for ( ;; ) {
887
888 consume_ws( reader );
889 JSON key;
890
891 if ( reader.peek() == '}' ) {
892 reader.get();
893 return object;
894 }
895 else if ( reader.peek() == '\"' ) {
896 key = parse_next(reader);
897 }
898 // The key isn't quoted, so assume it's a single word followed
899 // by a colon. Note that vanilla JSON requires all keys to be quoted,
900 // but Javascript object literals allow unquoted keys.
901 else {
902 std::string key_str;
903 char c;
904 while ( c = reader.get(), reader.good() ) {
905 if ( c == ':' || std::isspace(c) ) {
906 reader.unget( c );
907 break;
908 }
909 key_str += c;
910 }
911 key = key_str;
912 }
913
914 char next = get_next_char( reader );
915 if ( next != ':' ) {
916 issue_parse_error( next, "JSON object: Expected colon, found ", reader );
917 break;
918 }
919
920 consume_ws( reader );
921 JSON value = parse_next( reader );
922 object[ key.to_string() ] = value;
923
924 next = get_next_char( reader );
925 if ( next == ',' ) continue;
926 else if ( next == '}' ) break;
927 else {
928 issue_parse_error( next, "JSON object: Expected comma, found ", reader );
929 break;
930 }
931 }
932
933 return object;
934 }
935
936 JSON parse_array(StreamReader& reader) {
937 JSON array = JSON::make(JSON::DataType::Array);
938 unsigned index = 0;
939
940 for (;;) {
941
942 consume_ws(reader);
943 if (reader.peek() == ']') {
944 reader.get();
945 return array;
946 }
947
948 array[index++] = parse_next(reader);
949 consume_ws(reader);
950
951 char next = reader.get();
952 if (next == ',') continue;
953 else if (next == ']') break;
954 else {
955 issue_parse_error(next, "JSON array: Expected ',' or ']'"
956 ", found ", reader);
957 return JSON::make(JSON::DataType::Array);
958 }
959 }
960
961 return array;
962 }
963
964 JSON parse_string(StreamReader& reader) {
965 JSON str;
966 std::string val;
967 for(char c = reader.get(); c != '\"' && reader.good(); c = reader.get()) {
968 if (c == '\\') {
969 switch( reader.get() ) {
970 case '\"': val += '\"'; break;
971 case '\\': val += '\\'; break;
972 case '/' : val += '/' ; break;
973 case 'b' : val += '\b'; break;
974 case 'f' : val += '\f'; break;
975 case 'n' : val += '\n'; break;
976 case 'r' : val += '\r'; break;
977 case 't' : val += '\t'; break;
978 case 'u' : {
979 val += "\\u" ;
980 for(unsigned i = 1; i <= 4; ++i) {
981 c = reader.get();
982 if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
983 || (c >= 'A' && c <= 'F')) val += c;
984 else {
985 issue_parse_error(c, "JSON string: Expected hex character"
986 " in unicode escape, found ", reader);
987 return JSON::make(JSON::DataType::String);
988 }
989 }
990 break;
991 }
992 default: val += '\\'; break;
993 }
994 }
995 else val += c;
996 }
997 str = val;
998 return str;
999 }
1000
1001 JSON parse_number(StreamReader& reader, char old) {
1002 JSON Number;
1003 std::string val, exp_str;
1004 char c = old;
1005 bool isDouble = false;
1006 long exp = 0;
1007 for (;;) {
1008 if ( (c == '-') || (c >= '0' && c <= '9') )
1009 val += c;
1010 else if ( c == '.' ) {
1011 val += c;
1012 isDouble = true;
1013 }
1014 else
1015 break;
1016 c = reader.get();
1017 }
1018 if ( c == 'E' || c == 'e' ) {
1019 if ( reader.peek() == '-' ) { reader.get(); exp_str += '-'; }
1020 else if ( reader.peek() == '+' ) { reader.get(); }
1021 for (;;) {
1022 c = reader.get();
1023 if ( c >= '0' && c <= '9' )
1024 exp_str += c;
1025 else if ( !std::isspace( c ) && c != ',' && c != ']' && c != '}' ) {
1026 issue_parse_error(c, "JSON number: Expected a number for"
1027 " exponent, found ", reader);
1028 return JSON::make(JSON::DataType::Null);
1029 }
1030 else
1031 break;
1032 }
1033 exp = std::stol( exp_str );
1034 }
1035 else if ( !std::isspace( c ) && c != ',' && c != ']' && c != '}' ) {
1036 issue_parse_error(c, "JSON number: unexpected character ", reader);
1037 return JSON::make(JSON::DataType::Null);
1038 }
1039 reader.unget(c);
1040
1041 if ( isDouble )
1042 Number = std::stod( val ) * std::pow( 10, exp );
1043 else {
1044 if ( !exp_str.empty() )
1045 Number = std::stol( val ) * std::pow( 10, exp );
1046 else
1047 Number = std::stol( val );
1048 }
1049 return Number ;
1050 }
1051
1052 JSON parse_bool(StreamReader& reader, char old) {
1053 JSON b;
1054 std::string s(1, old);
1055 if (old == 't') {
1056 for (size_t i = 0; i < 3; ++i) s += reader.get();
1057 if (s == "true") b = true;
1058 }
1059 else if (old == 'f') {
1060 for (size_t i = 0; i < 4; ++i) s += reader.get();
1061 if (s == "false") b = false;
1062 }
1063 if (b.type() == JSON::DataType::Null) {
1064 // Get the entire string if the user supplied an invalid value
1065 while (reader.good() && !std::isspace(reader.peek())) s += reader.get();
1066 marley_utils::trim_inplace(s);
1067
1068 issue_parse_error(s, "JSON bool: Expected 'true' or 'false', found ",
1069 reader);
1070 return JSON::make(JSON::DataType::Null);
1071 }
1072 return b;
1073 }
1074
1075 JSON parse_null(StreamReader& reader) {
1076 JSON null;
1077 std::string s(1, 'n');
1078 for (size_t i = 0; i < 3; ++i) s += reader.get();
1079 if ( s != "null") {
1080 issue_parse_error("JSON null: Expected 'null', found ", s, reader);
1081 return JSON::make(JSON::DataType::Null);
1082 }
1083 return null;
1084 }
1085
1086 JSON parse_include( StreamReader& reader ) {
1087 std::string s( 1, '#' );
1088 for (size_t i = 0; i < 9; ++i) s += reader.get();
1089 if ( s != "#include:\"") {
1090 throw marley::Error( "JSON include: Expected 'include:\"', found '"
1091 + s + '\'' );
1092 return JSON::make( JSON::DataType::Null );
1093 }
1094
1095 // Parse the included file name into a temporary JSON object, then find
1096 // the full path to the file
1097 JSON file_name_json = parse_string( reader );
1098 std::string file_name = file_name_json.to_string();
1099
1100 const auto& fm = marley::FileManager::Instance();
1101 std::string full_file_name = fm.find_file( file_name );
1102
1103 if ( full_file_name.empty() ) {
1104 throw marley::Error( "Could not locate the included JSON file \""
1105 + file_name + "\". Please check that the file name is spelled"
1106 " correctly and that the file is in a folder on the MARLEY"
1107 " search path." );
1108 }
1109
1110 // Open the file for reading and check that it is ready to use
1111 auto included_file_stream
1112 = std::make_unique<std::ifstream>( full_file_name );
1113 if ( !included_file_stream->good() ) {
1114 throw marley::Error( "Could not read from the included JSON file \""
1115 + full_file_name + '\"' );
1116 }
1117
1118 // Create a child reader for the included file, chaining it via
1119 // the parent pointer to enable include-stack error traces
1120 StreamReader child_reader( std::move( included_file_stream ),
1121 full_file_name, &reader );
1122
1123 // Use a recursive call to parse_next() to interpret the JSON in the
1124 // file, allowing for the possibility of nested #include commands
1125 return parse_next( child_reader );
1126 }
1127
1128 JSON parse_next( StreamReader& reader ) {
1129 char value = get_next_char( reader );
1130 switch(value) {
1131 case '[' : return parse_array(reader);
1132 case '{' : return parse_object(reader);
1133 case '\"': return parse_string(reader);
1134 case 't' :
1135 case 'f' : return parse_bool(reader, value);
1136 case 'n' : return parse_null(reader);
1137 case '#' : return parse_include(reader);
1138 default :
1139 if ((value <= '9' && value >= '0') || value == '-')
1140 return parse_number(reader, value);
1141 }
1142 // Complain and throw an error if there was a problem
1143 if (!reader.good()) throw marley::Error("Unexpected end of JSON"
1144 " configuration file found\n");
1145 else throw marley::Error(std::string("JSON parse:")
1146 + " Unknown starting character '" + value + "'\n");
1147 return JSON();
1148 }
1149 }
1150
1151 inline JSON JSON::load_file(const std::string& filename) {
1152 auto stream = std::make_unique<std::ifstream>(filename);
1153 if (stream->good()) {
1154 StreamReader reader(std::move(stream), filename);
1155 return load(reader);
1156 }
1157 else {
1158 throw marley::Error("Could not open the file \"" + filename + "\"");
1159 return JSON::make(JSON::DataType::Null);
1160 }
1161 }
1162
1163 inline JSON JSON::load(std::istream& in) {
1164 StreamReader reader(in);
1165 return load(reader);
1166 }
1167
1168 inline JSON JSON::load(StreamReader& reader) {
1169 char first = get_next_char( reader );
1170 if (first != '{') {
1171 throw marley::Error("Missing '{' at beginning of JSON object");
1172 reader.unget(first);
1173 return parse_object(reader);
1174 }
1175 else {
1176 reader.unget(first);
1177 return parse_next(reader);
1178 }
1179 }
1180
1181 inline JSON JSON::load(const std::string& str) {
1182 std::stringstream iss(str);
1183 return load(iss);
1184 }
1185
1186}
1187
1188// Stream operators for JSON input and output using C++ streams
1189inline std::ostream& operator<<(std::ostream& os, const marley::JSON& json) {
1190 os << json.dump_string();
1191 return os;
1192}
1193
1194inline std::istream& operator>>(std::istream& is, marley::JSON& json) {
1195 json = marley::JSON::load(is);
1196 return is;
1197}
1198
1200// Utility function templates for retrieval of JSON data
1202
1203// Detects whether type T has a member function 'push_back' that is callable
1204// with an argument of T::value_type. Examples include std::vector,
1205// std::deque, etc.
1206template < typename T, typename = void > struct HasPushBack
1207 : std::false_type {};
1208
1209template < typename T > struct HasPushBack< T, std::void_t< decltype(
1210 std::declval< T >().push_back( std::declval< typename T::value_type >() )
1211 ) > > : std::true_type {};
1212
1213// Detects whether type T has a member function 'clear' that is callable with
1214// an argument of T::value_type. Examples include std::vector, std::deque,
1215// etc.
1216template < typename T, typename = void > struct HasClear
1217 : std::false_type {};
1218
1219template < typename T > struct HasClear< T, std::void_t< decltype(
1220 std::declval< T >().clear() ) > > : std::true_type {};
1221
1222// Detects whether type T has member types 'key_type' and 'mapped_type' (and is
1223// thus like a std::map or std::unordered_map. See
1224// https://stackoverflow.com/a/35293958/4081973 for more details.
1225template< typename T, typename = void > struct IsMappish : std::false_type { };
1226
1227template<typename T> struct IsMappish< T, std::void_t< typename T::key_type,
1228 typename T::mapped_type, decltype(
1229 std::declval< T& >()[ std::declval< const typename T::key_type& >() ] )
1230 > > : std::true_type { };
1231
1233template < typename T > bool convert_json( const marley::JSON& json,
1234 T& result )
1235{
1236 // Allow a trivial conversion to JSON itself (handy for populating a
1237 // container of JSON objects via recursive use of this function)
1238 if constexpr ( std::is_same_v< marley::JSON, T > ) {
1239 result = json;
1240 return true;
1241 }
1242
1243 // This option includes both integers and boolean types, so we need to
1244 // explicitly distinguish between them
1245 if constexpr ( std::is_integral_v< T > ) {
1246
1247 // If a boolean was requested just retrieve it and return
1248 if constexpr ( std::is_same_v< bool, T > ) {
1249 result = json.to_bool_or_throw();
1250 return true;
1251 }
1252
1253 // For other integer types, first retrieve a value as a long int (the
1254 // standard representation for marley::JSON integers)
1255 long temp_long = json.to_long_or_throw();
1256
1257 // If the requested type is unsigned, then double-check that we're not
1258 // working with a negative value stored in the JSON object. If we are,
1259 // then complain
1260 if constexpr ( std::is_unsigned_v< T > ) {
1261 if ( temp_long < 0 ) throw marley::Error( "Negative value "
1262 + std::to_string(temp_long) + " encountered when retrieving an"
1263 " unsigned integer from the JSON object '" + json.dump_string() + "'" );
1264 }
1265
1266 // Things seem ok, so cast to the desired output type before returning
1267 result = static_cast< T >( temp_long );
1268 return true;
1269 }
1270
1271 // For floating-point types, first retrieve a value as a double (the
1272 // standard representation for marley::JSON floating-point numbers)
1273 if constexpr ( std::is_floating_point_v< T > ) {
1274 double temp_double = json.to_double_or_throw();
1275
1276 // Do any needed type conversion (e.g., to float) via this cast, then
1277 // return
1278 result = static_cast< T >( temp_double );
1279 return true;
1280 }
1281
1282 if constexpr ( std::is_same_v< std::string, T > ) {
1283 result = json.to_string_or_throw();
1284 return true;
1285 }
1286
1287 // If we've been handed a container that implements the push_back() and
1288 // clear() methods, then we will assume a JSON array is meant to be parsed.
1289 // Note that although JSON arrays are allowed to contain elements of
1290 // multiple data types, this implementation assumes that they are all
1291 // representable as T::value_type.
1292 if constexpr ( HasPushBack< T >::value && HasClear< T >::value ) {
1293
1294 // Empty the container's existing contents
1295 result.clear();
1296
1297 // Complain if we're not actually working with a JSON array. Default
1298 // to using the input JSON object itself if we weren't handed a key.
1299 if ( !json.is_array() ) throw marley::Error( "Invalid JSON array '"
1300 + json.dump_string() + "'" );
1301
1302 // Fill the container with the JSON array elements using a dummy object
1303 // to allow for recursion
1304 auto elements = json.array_range();
1305 for ( const auto& el : elements ) {
1306
1307 typename T::value_type temp_val;
1308 bool ok = convert_json( el, temp_val );
1309
1310 // Save the element in the container if parsing went all right
1311 if ( ok ) result.push_back( temp_val );
1312 // Otherwise, complain
1313 else throw marley::Error( "Invalid array entry '" + el.dump_string()
1314 + "' found when parsing JSON array '" + json.dump_string() + "'" );
1315 }
1316
1317 return true;
1318 }
1319
1320 // If we've been handed a container that is "mappish" and implements the
1321 // clear() method, then we will assume a JSON object is meant to be parsed
1322 // into a map of key-value pairs. Note that this implementation requires
1323 // elements of the JSON object to have the same data type.
1324 if constexpr ( IsMappish< T >::value && HasClear< T >::value ) {
1325
1326 // Empty the container's existing contents
1327 result.clear();
1328
1329 // Complain if we're not actually working with a JSON object. Default
1330 // to using the input JSON object itself if we weren't handed a key.
1331 if ( !json.is_object() ) throw marley::Error( "Invalid JSON object '"
1332 + json.dump_string() + "'" );
1333
1334 // Fill the container with the JSON object elements using recursion
1335 auto elements = json.object_range();
1336 for ( const auto& el : elements ) {
1337
1338 std::string element_key = el.first;
1339 const marley::JSON& element_json = el.second;
1340
1341 typename T::mapped_type temp_val;
1342 bool ok = convert_json( element_json, temp_val );
1343
1344 // Save the element in the container if parsing went all right
1345 if ( ok ) result[ element_key ] = temp_val;
1346 // Otherwise, complain
1347 else throw marley::Error( "Invalid map entry '"
1348 + element_json.dump_string() + "' found when parsing JSON object '"
1349 + json.dump_string() + "'" );
1350 }
1351
1352 return true;
1353 }
1354
1355 // If we get here, then no conversion is implemented for the requested data
1356 // type
1357 return false;
1358}
1359
1360// Alternate version that provides a default value if conversion fails
1361template < typename T > bool convert_json( const marley::JSON& json,
1362 T& result, T def_val )
1363{
1364 bool ok = convert_json( json, result );
1365 if ( !ok ) result = def_val;
1366 return ok;
1367}
1368
1369// Alternate version that returns the converted value or a default one while
1370// storing the boolean flag indicating success (true) or failure (false)
1371template < typename T > T assign_from_json( const marley::JSON& json,
1372 bool& ok, T def_val = T() )
1373{
1374 T temp;
1375 ok = convert_json( json, temp, def_val );
1376 return temp;
1377}
1378
1380template < typename T > bool get_from_json( const std::string& key,
1381 const marley::JSON& json, T& result )
1382{
1383 json.check_if_object( key );
1384 if ( !json.has_key(key) ) return false;
1385 const marley::JSON& element = json.at( key );
1386 return convert_json( element, result );
1387}
1388
1389// Alternate version that provides a default value if retrieval fails
1390template < typename T > bool get_from_json( const std::string& key,
1391 const marley::JSON& json, T& result, T def_val )
1392{
1393 bool ok = get_from_json( key, json, result );
1394 if ( !ok ) result = def_val;
1395 return ok;
1396}
1397
1398// Alternate version that returns the retrieved value or a default one while
1399// storing the boolean flag indicating success (true) or failure (false)
1400template < typename T > T assign_from_json( const std::string& key,
1401 const marley::JSON& json, bool& ok, T def_val = T() )
1402{
1403 T temp;
1404 ok = get_from_json( key, json, temp, def_val );
1405 return temp;
1406}
Base class for all exceptions thrown by MARLEY functions.
Definition Error.hh:26
static const FileManager & Instance()
Get a const reference to the singleton instance of the FileManager.
bool is_null() const
Functions for getting primitives from the JSON object.
Definition JSON.hh:352