1 /**
2 * @file cpptoml.hpp
3 * @author Chase Geigle
4 * @date May 2013
5 */
6
7 #ifndef CPPTOML_H
8 #define CPPTOML_H
9
10 #include <algorithm>
11 #include <cassert>
12 #include <clocale>
13 #include <cstdint>
14 #include <cstring>
15 #include <fstream>
16 #include <iomanip>
17 #include <map>
18 #include <memory>
19 #include <sstream>
20 #include <stdexcept>
21 #include <string>
22 #include <unordered_map>
23 #include <vector>
24
25 #if __cplusplus > 201103L
26 #define CPPTOML_DEPRECATED(reason) [[deprecated(reason)]]
27 #elif defined(__clang__)
28 #define CPPTOML_DEPRECATED(reason) __attribute__((deprecated(reason)))
29 #else
30 #define CPPTOML_DEPRECATED(reason) __attribute__((deprecated))
31 #endif
32
33 namespace cpptoml
34 {
35 class writer; // forward declaration
36 class base; // forward declaration
37 #if defined(CPPTOML_USE_MAP)
38 // a std::map will ensure that entries a sorted, albeit at a slight
39 // performance penalty relative to the (default) unordered_map
40 using string_to_base_map = std::map<std::string, std::shared_ptr<base>>;
41 #else
42 // by default an unordered_map is used for best performance as the
43 // toml specification does not require entries to be sorted
44 using string_to_base_map
45 = std::unordered_map<std::string, std::shared_ptr<base>>;
46 #endif
47
48 // if defined, `base` will retain type information in form of an enum class
49 // such that static_cast can be used instead of dynamic_cast
50 // #define CPPTOML_NO_RTTI
51
52 template <class T>
53 class option
54 {
55 public:
option()56 option() : empty_{true}
57 {
58 // nothing
59 }
60
option(T value)61 option(T value) : empty_{false}, value_(std::move(value))
62 {
63 // nothing
64 }
65
operator bool() const66 explicit operator bool() const
67 {
68 return !empty_;
69 }
70
operator *() const71 const T& operator*() const
72 {
73 return value_;
74 }
75
operator ->() const76 const T* operator->() const
77 {
78 return &value_;
79 }
80
81 template <class U>
value_or(U && alternative) const82 T value_or(U&& alternative) const
83 {
84 if (!empty_)
85 return value_;
86 return static_cast<T>(std::forward<U>(alternative));
87 }
88
89 private:
90 bool empty_;
91 T value_;
92 };
93
94 struct local_date
95 {
96 int year = 0;
97 int month = 0;
98 int day = 0;
99 };
100
101 struct local_time
102 {
103 int hour = 0;
104 int minute = 0;
105 int second = 0;
106 int microsecond = 0;
107 };
108
109 struct zone_offset
110 {
111 int hour_offset = 0;
112 int minute_offset = 0;
113 };
114
115 struct local_datetime : local_date, local_time
116 {
117 };
118
119 struct offset_datetime : local_datetime, zone_offset
120 {
from_zonedcpptoml::offset_datetime121 static inline struct offset_datetime from_zoned(const struct tm& t)
122 {
123 offset_datetime dt;
124 dt.year = t.tm_year + 1900;
125 dt.month = t.tm_mon + 1;
126 dt.day = t.tm_mday;
127 dt.hour = t.tm_hour;
128 dt.minute = t.tm_min;
129 dt.second = t.tm_sec;
130
131 char buf[16];
132 strftime(buf, 16, "%z", &t);
133
134 int offset = std::stoi(buf);
135 dt.hour_offset = offset / 100;
136 dt.minute_offset = offset % 100;
137 return dt;
138 }
139
140 CPPTOML_DEPRECATED("from_local has been renamed to from_zoned")
from_localcpptoml::offset_datetime141 static inline struct offset_datetime from_local(const struct tm& t)
142 {
143 return from_zoned(t);
144 }
145
from_utccpptoml::offset_datetime146 static inline struct offset_datetime from_utc(const struct tm& t)
147 {
148 offset_datetime dt;
149 dt.year = t.tm_year + 1900;
150 dt.month = t.tm_mon + 1;
151 dt.day = t.tm_mday;
152 dt.hour = t.tm_hour;
153 dt.minute = t.tm_min;
154 dt.second = t.tm_sec;
155 return dt;
156 }
157 };
158
159 CPPTOML_DEPRECATED("datetime has been renamed to offset_datetime")
160 typedef offset_datetime datetime;
161
162 class fill_guard
163 {
164 public:
fill_guard(std::ostream & os)165 fill_guard(std::ostream& os) : os_(os), fill_{os.fill()}
166 {
167 // nothing
168 }
169
~fill_guard()170 ~fill_guard()
171 {
172 os_.fill(fill_);
173 }
174
175 private:
176 std::ostream& os_;
177 std::ostream::char_type fill_;
178 };
179
operator <<(std::ostream & os,const local_date & dt)180 inline std::ostream& operator<<(std::ostream& os, const local_date& dt)
181 {
182 fill_guard g{os};
183 os.fill('0');
184
185 using std::setw;
186 os << setw(4) << dt.year << "-" << setw(2) << dt.month << "-" << setw(2)
187 << dt.day;
188
189 return os;
190 }
191
operator <<(std::ostream & os,const local_time & ltime)192 inline std::ostream& operator<<(std::ostream& os, const local_time& ltime)
193 {
194 fill_guard g{os};
195 os.fill('0');
196
197 using std::setw;
198 os << setw(2) << ltime.hour << ":" << setw(2) << ltime.minute << ":"
199 << setw(2) << ltime.second;
200
201 if (ltime.microsecond > 0)
202 {
203 os << ".";
204 int power = 100000;
205 for (int curr_us = ltime.microsecond; curr_us; power /= 10)
206 {
207 auto num = curr_us / power;
208 os << num;
209 curr_us -= num * power;
210 }
211 }
212
213 return os;
214 }
215
operator <<(std::ostream & os,const zone_offset & zo)216 inline std::ostream& operator<<(std::ostream& os, const zone_offset& zo)
217 {
218 fill_guard g{os};
219 os.fill('0');
220
221 using std::setw;
222
223 if (zo.hour_offset != 0 || zo.minute_offset != 0)
224 {
225 if (zo.hour_offset > 0)
226 {
227 os << "+";
228 }
229 else
230 {
231 os << "-";
232 }
233 os << setw(2) << std::abs(zo.hour_offset) << ":" << setw(2)
234 << std::abs(zo.minute_offset);
235 }
236 else
237 {
238 os << "Z";
239 }
240
241 return os;
242 }
243
operator <<(std::ostream & os,const local_datetime & dt)244 inline std::ostream& operator<<(std::ostream& os, const local_datetime& dt)
245 {
246 return os << static_cast<const local_date&>(dt) << "T"
247 << static_cast<const local_time&>(dt);
248 }
249
operator <<(std::ostream & os,const offset_datetime & dt)250 inline std::ostream& operator<<(std::ostream& os, const offset_datetime& dt)
251 {
252 return os << static_cast<const local_datetime&>(dt)
253 << static_cast<const zone_offset&>(dt);
254 }
255
256 template <class T, class... Ts>
257 struct is_one_of;
258
259 template <class T, class V>
260 struct is_one_of<T, V> : std::is_same<T, V>
261 {
262 };
263
264 template <class T, class V, class... Ts>
265 struct is_one_of<T, V, Ts...>
266 {
267 const static bool value
268 = std::is_same<T, V>::value || is_one_of<T, Ts...>::value;
269 };
270
271 template <class T>
272 class value;
273
274 template <class T>
275 struct valid_value
276 : is_one_of<T, std::string, int64_t, double, bool, local_date, local_time,
277 local_datetime, offset_datetime>
278 {
279 };
280
281 template <class T, class Enable = void>
282 struct value_traits;
283
284 template <class T>
285 struct valid_value_or_string_convertible
286 {
287
288 const static bool value = valid_value<typename std::decay<T>::type>::value
289 || std::is_convertible<T, std::string>::value;
290 };
291
292 template <class T>
293 struct value_traits<T, typename std::enable_if<
294 valid_value_or_string_convertible<T>::value>::type>
295 {
296 using value_type = typename std::conditional<
297 valid_value<typename std::decay<T>::type>::value,
298 typename std::decay<T>::type, std::string>::type;
299
300 using type = value<value_type>;
301
constructcpptoml::value_traits302 static value_type construct(T&& val)
303 {
304 return value_type(val);
305 }
306 };
307
308 template <class T>
309 struct value_traits<
310 T,
311 typename std::enable_if<
312 !valid_value_or_string_convertible<T>::value
313 && std::is_floating_point<typename std::decay<T>::type>::value>::type>
314 {
315 using value_type = typename std::decay<T>::type;
316
317 using type = value<double>;
318
constructcpptoml::value_traits319 static value_type construct(T&& val)
320 {
321 return value_type(val);
322 }
323 };
324
325 template <class T>
326 struct value_traits<
327 T, typename std::enable_if<
328 !valid_value_or_string_convertible<T>::value
329 && !std::is_floating_point<typename std::decay<T>::type>::value
330 && std::is_signed<typename std::decay<T>::type>::value>::type>
331 {
332 using value_type = int64_t;
333
334 using type = value<int64_t>;
335
constructcpptoml::value_traits336 static value_type construct(T&& val)
337 {
338 if (val < (std::numeric_limits<int64_t>::min)())
339 throw std::underflow_error{"constructed value cannot be "
340 "represented by a 64-bit signed "
341 "integer"};
342
343 if (val > (std::numeric_limits<int64_t>::max)())
344 throw std::overflow_error{"constructed value cannot be represented "
345 "by a 64-bit signed integer"};
346
347 return static_cast<int64_t>(val);
348 }
349 };
350
351 template <class T>
352 struct value_traits<
353 T, typename std::enable_if<
354 !valid_value_or_string_convertible<T>::value
355 && std::is_unsigned<typename std::decay<T>::type>::value>::type>
356 {
357 using value_type = int64_t;
358
359 using type = value<int64_t>;
360
constructcpptoml::value_traits361 static value_type construct(T&& val)
362 {
363 if (val > static_cast<uint64_t>((std::numeric_limits<int64_t>::max)()))
364 throw std::overflow_error{"constructed value cannot be represented "
365 "by a 64-bit signed integer"};
366
367 return static_cast<int64_t>(val);
368 }
369 };
370
371 class array;
372 class table;
373 class table_array;
374
375 template <class T>
376 struct array_of_trait
377 {
378 using return_type = option<std::vector<T>>;
379 };
380
381 template <>
382 struct array_of_trait<array>
383 {
384 using return_type = option<std::vector<std::shared_ptr<array>>>;
385 };
386
387 template <class T>
388 inline std::shared_ptr<typename value_traits<T>::type> make_value(T&& val);
389 inline std::shared_ptr<array> make_array();
390
391 namespace detail
392 {
393 template <class T>
394 inline std::shared_ptr<T> make_element();
395 }
396
397 inline std::shared_ptr<table> make_table();
398 inline std::shared_ptr<table_array> make_table_array(bool is_inline = false);
399
400 #if defined(CPPTOML_NO_RTTI)
401 /// Base type used to store underlying data type explicitly if RTTI is disabled
402 enum class base_type
403 {
404 NONE,
405 STRING,
406 LOCAL_TIME,
407 LOCAL_DATE,
408 LOCAL_DATETIME,
409 OFFSET_DATETIME,
410 INT,
411 FLOAT,
412 BOOL,
413 TABLE,
414 ARRAY,
415 TABLE_ARRAY
416 };
417
418 /// Type traits class to convert C++ types to enum member
419 template <class T>
420 struct base_type_traits;
421
422 template <>
423 struct base_type_traits<std::string>
424 {
425 static const base_type type = base_type::STRING;
426 };
427
428 template <>
429 struct base_type_traits<local_time>
430 {
431 static const base_type type = base_type::LOCAL_TIME;
432 };
433
434 template <>
435 struct base_type_traits<local_date>
436 {
437 static const base_type type = base_type::LOCAL_DATE;
438 };
439
440 template <>
441 struct base_type_traits<local_datetime>
442 {
443 static const base_type type = base_type::LOCAL_DATETIME;
444 };
445
446 template <>
447 struct base_type_traits<offset_datetime>
448 {
449 static const base_type type = base_type::OFFSET_DATETIME;
450 };
451
452 template <>
453 struct base_type_traits<int64_t>
454 {
455 static const base_type type = base_type::INT;
456 };
457
458 template <>
459 struct base_type_traits<double>
460 {
461 static const base_type type = base_type::FLOAT;
462 };
463
464 template <>
465 struct base_type_traits<bool>
466 {
467 static const base_type type = base_type::BOOL;
468 };
469
470 template <>
471 struct base_type_traits<table>
472 {
473 static const base_type type = base_type::TABLE;
474 };
475
476 template <>
477 struct base_type_traits<array>
478 {
479 static const base_type type = base_type::ARRAY;
480 };
481
482 template <>
483 struct base_type_traits<table_array>
484 {
485 static const base_type type = base_type::TABLE_ARRAY;
486 };
487 #endif
488
489 /**
490 * A generic base TOML value used for type erasure.
491 */
492 class base : public std::enable_shared_from_this<base>
493 {
494 public:
495 virtual ~base() = default;
496
497 virtual std::shared_ptr<base> clone() const = 0;
498
499 /**
500 * Determines if the given TOML element is a value.
501 */
is_value() const502 virtual bool is_value() const
503 {
504 return false;
505 }
506
507 /**
508 * Determines if the given TOML element is a table.
509 */
is_table() const510 virtual bool is_table() const
511 {
512 return false;
513 }
514
515 /**
516 * Converts the TOML element into a table.
517 */
as_table()518 std::shared_ptr<table> as_table()
519 {
520 if (is_table())
521 return std::static_pointer_cast<table>(shared_from_this());
522 return nullptr;
523 }
524 /**
525 * Determines if the TOML element is an array of "leaf" elements.
526 */
is_array() const527 virtual bool is_array() const
528 {
529 return false;
530 }
531
532 /**
533 * Converts the TOML element to an array.
534 */
as_array()535 std::shared_ptr<array> as_array()
536 {
537 if (is_array())
538 return std::static_pointer_cast<array>(shared_from_this());
539 return nullptr;
540 }
541
542 /**
543 * Determines if the given TOML element is an array of tables.
544 */
is_table_array() const545 virtual bool is_table_array() const
546 {
547 return false;
548 }
549
550 /**
551 * Converts the TOML element into a table array.
552 */
as_table_array()553 std::shared_ptr<table_array> as_table_array()
554 {
555 if (is_table_array())
556 return std::static_pointer_cast<table_array>(shared_from_this());
557 return nullptr;
558 }
559
560 /**
561 * Attempts to coerce the TOML element into a concrete TOML value
562 * of type T.
563 */
564 template <class T>
565 std::shared_ptr<value<T>> as();
566
567 template <class T>
568 std::shared_ptr<const value<T>> as() const;
569
570 template <class Visitor, class... Args>
571 void accept(Visitor&& visitor, Args&&... args) const;
572
573 #if defined(CPPTOML_NO_RTTI)
type() const574 base_type type() const
575 {
576 return type_;
577 }
578
579 protected:
base(const base_type t)580 base(const base_type t) : type_(t)
581 {
582 // nothing
583 }
584
585 private:
586 const base_type type_ = base_type::NONE;
587
588 #else
589 protected:
base()590 base()
591 {
592 // nothing
593 }
594 #endif
595 };
596
597 /**
598 * A concrete TOML value representing the "leaves" of the "tree".
599 */
600 template <class T>
601 class value : public base
602 {
603 struct make_shared_enabler
604 {
605 // nothing; this is a private key accessible only to friends
606 };
607
608 template <class U>
609 friend std::shared_ptr<typename value_traits<U>::type>
610 cpptoml::make_value(U&& val);
611
612 public:
613 static_assert(valid_value<T>::value, "invalid value type");
614
615 std::shared_ptr<base> clone() const override;
616
value(const make_shared_enabler &,const T & val)617 value(const make_shared_enabler&, const T& val) : value(val)
618 {
619 // nothing; note that users cannot actually invoke this function
620 // because they lack access to the make_shared_enabler.
621 }
622
is_value() const623 bool is_value() const override
624 {
625 return true;
626 }
627
628 /**
629 * Gets the data associated with this value.
630 */
get()631 T& get()
632 {
633 return data_;
634 }
635
636 /**
637 * Gets the data associated with this value. Const version.
638 */
get() const639 const T& get() const
640 {
641 return data_;
642 }
643
644 private:
645 T data_;
646
647 /**
648 * Constructs a value from the given data.
649 */
650 #if defined(CPPTOML_NO_RTTI)
value(const T & val)651 value(const T& val) : base(base_type_traits<T>::type), data_(val)
652 {
653 }
654 #else
value(const T & val)655 value(const T& val) : data_(val)
656 {
657 }
658 #endif
659
660 value(const value& val) = delete;
661 value& operator=(const value& val) = delete;
662 };
663
664 template <class T>
make_value(T && val)665 std::shared_ptr<typename value_traits<T>::type> make_value(T&& val)
666 {
667 using value_type = typename value_traits<T>::type;
668 using enabler = typename value_type::make_shared_enabler;
669 return std::make_shared<value_type>(
670 enabler{}, value_traits<T>::construct(std::forward<T>(val)));
671 }
672
673 template <class T>
as()674 inline std::shared_ptr<value<T>> base::as()
675 {
676 #if defined(CPPTOML_NO_RTTI)
677 if (type() == base_type_traits<T>::type)
678 return std::static_pointer_cast<value<T>>(shared_from_this());
679 else
680 return nullptr;
681 #else
682 return std::dynamic_pointer_cast<value<T>>(shared_from_this());
683 #endif
684 }
685
686 // special case value<double> to allow getting an integer parameter as a
687 // double value
688 template <>
as()689 inline std::shared_ptr<value<double>> base::as()
690 {
691 #if defined(CPPTOML_NO_RTTI)
692 if (type() == base_type::FLOAT)
693 return std::static_pointer_cast<value<double>>(shared_from_this());
694
695 if (type() == base_type::INT)
696 {
697 auto v = std::static_pointer_cast<value<int64_t>>(shared_from_this());
698 return make_value<double>(static_cast<double>(v->get()));
699 }
700 #else
701 if (auto v = std::dynamic_pointer_cast<value<double>>(shared_from_this()))
702 return v;
703
704 if (auto v = std::dynamic_pointer_cast<value<int64_t>>(shared_from_this()))
705 return make_value<double>(static_cast<double>(v->get()));
706 #endif
707
708 return nullptr;
709 }
710
711 template <class T>
as() const712 inline std::shared_ptr<const value<T>> base::as() const
713 {
714 #if defined(CPPTOML_NO_RTTI)
715 if (type() == base_type_traits<T>::type)
716 return std::static_pointer_cast<const value<T>>(shared_from_this());
717 else
718 return nullptr;
719 #else
720 return std::dynamic_pointer_cast<const value<T>>(shared_from_this());
721 #endif
722 }
723
724 // special case value<double> to allow getting an integer parameter as a
725 // double value
726 template <>
as() const727 inline std::shared_ptr<const value<double>> base::as() const
728 {
729 #if defined(CPPTOML_NO_RTTI)
730 if (type() == base_type::FLOAT)
731 return std::static_pointer_cast<const value<double>>(
732 shared_from_this());
733
734 if (type() == base_type::INT)
735 {
736 auto v = as<int64_t>();
737 // the below has to be a non-const value<double> due to a bug in
738 // libc++: https://llvm.org/bugs/show_bug.cgi?id=18843
739 return make_value<double>(static_cast<double>(v->get()));
740 }
741 #else
742 if (auto v
743 = std::dynamic_pointer_cast<const value<double>>(shared_from_this()))
744 return v;
745
746 if (auto v = as<int64_t>())
747 {
748 // the below has to be a non-const value<double> due to a bug in
749 // libc++: https://llvm.org/bugs/show_bug.cgi?id=18843
750 return make_value<double>(static_cast<double>(v->get()));
751 }
752 #endif
753
754 return nullptr;
755 }
756
757 /**
758 * Exception class for array insertion errors.
759 */
760 class array_exception : public std::runtime_error
761 {
762 public:
array_exception(const std::string & err)763 array_exception(const std::string& err) : std::runtime_error{err}
764 {
765 }
766 };
767
768 class array : public base
769 {
770 public:
771 friend std::shared_ptr<array> make_array();
772
773 std::shared_ptr<base> clone() const override;
774
is_array() const775 virtual bool is_array() const override
776 {
777 return true;
778 }
779
780 using size_type = std::size_t;
781
782 /**
783 * arrays can be iterated over
784 */
785 using iterator = std::vector<std::shared_ptr<base>>::iterator;
786
787 /**
788 * arrays can be iterated over. Const version.
789 */
790 using const_iterator = std::vector<std::shared_ptr<base>>::const_iterator;
791
begin()792 iterator begin()
793 {
794 return values_.begin();
795 }
796
begin() const797 const_iterator begin() const
798 {
799 return values_.begin();
800 }
801
end()802 iterator end()
803 {
804 return values_.end();
805 }
806
end() const807 const_iterator end() const
808 {
809 return values_.end();
810 }
811
812 /**
813 * Obtains the array (vector) of base values.
814 */
get()815 std::vector<std::shared_ptr<base>>& get()
816 {
817 return values_;
818 }
819
820 /**
821 * Obtains the array (vector) of base values. Const version.
822 */
get() const823 const std::vector<std::shared_ptr<base>>& get() const
824 {
825 return values_;
826 }
827
at(size_t idx) const828 std::shared_ptr<base> at(size_t idx) const
829 {
830 return values_.at(idx);
831 }
832
833 /**
834 * Obtains an array of value<T>s. Note that elements may be
835 * nullptr if they cannot be converted to a value<T>.
836 */
837 template <class T>
array_of() const838 std::vector<std::shared_ptr<value<T>>> array_of() const
839 {
840 std::vector<std::shared_ptr<value<T>>> result(values_.size());
841
842 std::transform(values_.begin(), values_.end(), result.begin(),
843 [&](std::shared_ptr<base> v) { return v->as<T>(); });
844
845 return result;
846 }
847
848 /**
849 * Obtains a option<vector<T>>. The option will be empty if the array
850 * contains values that are not of type T.
851 */
852 template <class T>
get_array_of() const853 inline typename array_of_trait<T>::return_type get_array_of() const
854 {
855 std::vector<T> result;
856 result.reserve(values_.size());
857
858 for (const auto& val : values_)
859 {
860 if (auto v = val->as<T>())
861 result.push_back(v->get());
862 else
863 return {};
864 }
865
866 return {std::move(result)};
867 }
868
869 /**
870 * Obtains an array of arrays. Note that elements may be nullptr
871 * if they cannot be converted to a array.
872 */
nested_array() const873 std::vector<std::shared_ptr<array>> nested_array() const
874 {
875 std::vector<std::shared_ptr<array>> result(values_.size());
876
877 std::transform(values_.begin(), values_.end(), result.begin(),
878 [&](std::shared_ptr<base> v) -> std::shared_ptr<array> {
879 if (v->is_array())
880 return std::static_pointer_cast<array>(v);
881 return std::shared_ptr<array>{};
882 });
883
884 return result;
885 }
886
887 /**
888 * Add a value to the end of the array
889 */
890 template <class T>
push_back(const std::shared_ptr<value<T>> & val)891 void push_back(const std::shared_ptr<value<T>>& val)
892 {
893 if (values_.empty() || values_[0]->as<T>())
894 {
895 values_.push_back(val);
896 }
897 else
898 {
899 throw array_exception{"Arrays must be homogenous."};
900 }
901 }
902
903 /**
904 * Add an array to the end of the array
905 */
push_back(const std::shared_ptr<array> & val)906 void push_back(const std::shared_ptr<array>& val)
907 {
908 if (values_.empty() || values_[0]->is_array())
909 {
910 values_.push_back(val);
911 }
912 else
913 {
914 throw array_exception{"Arrays must be homogenous."};
915 }
916 }
917
918 /**
919 * Convenience function for adding a simple element to the end
920 * of the array.
921 */
922 template <class T>
push_back(T && val,typename value_traits<T>::type * =0)923 void push_back(T&& val, typename value_traits<T>::type* = 0)
924 {
925 push_back(make_value(std::forward<T>(val)));
926 }
927
928 /**
929 * Insert a value into the array
930 */
931 template <class T>
insert(iterator position,const std::shared_ptr<value<T>> & value)932 iterator insert(iterator position, const std::shared_ptr<value<T>>& value)
933 {
934 if (values_.empty() || values_[0]->as<T>())
935 {
936 return values_.insert(position, value);
937 }
938 else
939 {
940 throw array_exception{"Arrays must be homogenous."};
941 }
942 }
943
944 /**
945 * Insert an array into the array
946 */
insert(iterator position,const std::shared_ptr<array> & value)947 iterator insert(iterator position, const std::shared_ptr<array>& value)
948 {
949 if (values_.empty() || values_[0]->is_array())
950 {
951 return values_.insert(position, value);
952 }
953 else
954 {
955 throw array_exception{"Arrays must be homogenous."};
956 }
957 }
958
959 /**
960 * Convenience function for inserting a simple element in the array
961 */
962 template <class T>
insert(iterator position,T && val,typename value_traits<T>::type * =0)963 iterator insert(iterator position, T&& val,
964 typename value_traits<T>::type* = 0)
965 {
966 return insert(position, make_value(std::forward<T>(val)));
967 }
968
969 /**
970 * Erase an element from the array
971 */
erase(iterator position)972 iterator erase(iterator position)
973 {
974 return values_.erase(position);
975 }
976
977 /**
978 * Clear the array
979 */
clear()980 void clear()
981 {
982 values_.clear();
983 }
984
985 /**
986 * Reserve space for n values.
987 */
reserve(size_type n)988 void reserve(size_type n)
989 {
990 values_.reserve(n);
991 }
992
993 private:
994 #if defined(CPPTOML_NO_RTTI)
array()995 array() : base(base_type::ARRAY)
996 {
997 // empty
998 }
999 #else
1000 array() = default;
1001 #endif
1002
1003 template <class InputIterator>
array(InputIterator begin,InputIterator end)1004 array(InputIterator begin, InputIterator end) : values_{begin, end}
1005 {
1006 // nothing
1007 }
1008
1009 array(const array& obj) = delete;
1010 array& operator=(const array& obj) = delete;
1011
1012 std::vector<std::shared_ptr<base>> values_;
1013 };
1014
make_array()1015 inline std::shared_ptr<array> make_array()
1016 {
1017 struct make_shared_enabler : public array
1018 {
1019 make_shared_enabler()
1020 {
1021 // nothing
1022 }
1023 };
1024
1025 return std::make_shared<make_shared_enabler>();
1026 }
1027
1028 namespace detail
1029 {
1030 template <>
make_element()1031 inline std::shared_ptr<array> make_element<array>()
1032 {
1033 return make_array();
1034 }
1035 } // namespace detail
1036
1037 /**
1038 * Obtains a option<vector<T>>. The option will be empty if the array
1039 * contains values that are not of type T.
1040 */
1041 template <>
1042 inline typename array_of_trait<array>::return_type
get_array_of() const1043 array::get_array_of<array>() const
1044 {
1045 std::vector<std::shared_ptr<array>> result;
1046 result.reserve(values_.size());
1047
1048 for (const auto& val : values_)
1049 {
1050 if (auto v = val->as_array())
1051 result.push_back(v);
1052 else
1053 return {};
1054 }
1055
1056 return {std::move(result)};
1057 }
1058
1059 class table;
1060
1061 class table_array : public base
1062 {
1063 friend class table;
1064 friend std::shared_ptr<table_array> make_table_array(bool);
1065
1066 public:
1067 std::shared_ptr<base> clone() const override;
1068
1069 using size_type = std::size_t;
1070
1071 /**
1072 * arrays can be iterated over
1073 */
1074 using iterator = std::vector<std::shared_ptr<table>>::iterator;
1075
1076 /**
1077 * arrays can be iterated over. Const version.
1078 */
1079 using const_iterator = std::vector<std::shared_ptr<table>>::const_iterator;
1080
begin()1081 iterator begin()
1082 {
1083 return array_.begin();
1084 }
1085
begin() const1086 const_iterator begin() const
1087 {
1088 return array_.begin();
1089 }
1090
end()1091 iterator end()
1092 {
1093 return array_.end();
1094 }
1095
end() const1096 const_iterator end() const
1097 {
1098 return array_.end();
1099 }
1100
is_table_array() const1101 virtual bool is_table_array() const override
1102 {
1103 return true;
1104 }
1105
get()1106 std::vector<std::shared_ptr<table>>& get()
1107 {
1108 return array_;
1109 }
1110
get() const1111 const std::vector<std::shared_ptr<table>>& get() const
1112 {
1113 return array_;
1114 }
1115
1116 /**
1117 * Add a table to the end of the array
1118 */
push_back(const std::shared_ptr<table> & val)1119 void push_back(const std::shared_ptr<table>& val)
1120 {
1121 array_.push_back(val);
1122 }
1123
1124 /**
1125 * Insert a table into the array
1126 */
insert(iterator position,const std::shared_ptr<table> & value)1127 iterator insert(iterator position, const std::shared_ptr<table>& value)
1128 {
1129 return array_.insert(position, value);
1130 }
1131
1132 /**
1133 * Erase an element from the array
1134 */
erase(iterator position)1135 iterator erase(iterator position)
1136 {
1137 return array_.erase(position);
1138 }
1139
1140 /**
1141 * Clear the array
1142 */
clear()1143 void clear()
1144 {
1145 array_.clear();
1146 }
1147
1148 /**
1149 * Reserve space for n tables.
1150 */
reserve(size_type n)1151 void reserve(size_type n)
1152 {
1153 array_.reserve(n);
1154 }
1155
1156 /**
1157 * Whether or not the table array is declared inline. This mostly
1158 * matters for parsing, where statically defined arrays cannot be
1159 * appended to using the array-of-table syntax.
1160 */
is_inline() const1161 bool is_inline() const
1162 {
1163 return is_inline_;
1164 }
1165
1166 private:
1167 #if defined(CPPTOML_NO_RTTI)
table_array(bool is_inline=false)1168 table_array(bool is_inline = false)
1169 : base(base_type::TABLE_ARRAY), is_inline_(is_inline)
1170 {
1171 // nothing
1172 }
1173 #else
1174 table_array(bool is_inline = false) : is_inline_(is_inline)
1175 {
1176 // nothing
1177 }
1178 #endif
1179
1180 table_array(const table_array& obj) = delete;
1181 table_array& operator=(const table_array& rhs) = delete;
1182
1183 std::vector<std::shared_ptr<table>> array_;
1184 const bool is_inline_ = false;
1185 };
1186
make_table_array(bool is_inline)1187 inline std::shared_ptr<table_array> make_table_array(bool is_inline)
1188 {
1189 struct make_shared_enabler : public table_array
1190 {
1191 make_shared_enabler(bool mse_is_inline) : table_array(mse_is_inline)
1192 {
1193 // nothing
1194 }
1195 };
1196
1197 return std::make_shared<make_shared_enabler>(is_inline);
1198 }
1199
1200 namespace detail
1201 {
1202 template <>
make_element()1203 inline std::shared_ptr<table_array> make_element<table_array>()
1204 {
1205 return make_table_array(true);
1206 }
1207 } // namespace detail
1208
1209 // The below are overloads for fetching specific value types out of a value
1210 // where special casting behavior (like bounds checking) is desired
1211
1212 template <class T>
1213 typename std::enable_if<!std::is_floating_point<T>::value
1214 && std::is_signed<T>::value,
1215 option<T>>::type
get_impl(const std::shared_ptr<base> & elem)1216 get_impl(const std::shared_ptr<base>& elem)
1217 {
1218 if (auto v = elem->as<int64_t>())
1219 {
1220 if (v->get() < (std::numeric_limits<T>::min)())
1221 throw std::underflow_error{
1222 "T cannot represent the value requested in get"};
1223
1224 if (v->get() > (std::numeric_limits<T>::max)())
1225 throw std::overflow_error{
1226 "T cannot represent the value requested in get"};
1227
1228 return {static_cast<T>(v->get())};
1229 }
1230 else
1231 {
1232 return {};
1233 }
1234 }
1235
1236 template <class T>
1237 typename std::enable_if<!std::is_same<T, bool>::value
1238 && std::is_unsigned<T>::value,
1239 option<T>>::type
get_impl(const std::shared_ptr<base> & elem)1240 get_impl(const std::shared_ptr<base>& elem)
1241 {
1242 if (auto v = elem->as<int64_t>())
1243 {
1244 if (v->get() < 0)
1245 throw std::underflow_error{"T cannot store negative value in get"};
1246
1247 if (static_cast<uint64_t>(v->get()) > (std::numeric_limits<T>::max)())
1248 throw std::overflow_error{
1249 "T cannot represent the value requested in get"};
1250
1251 return {static_cast<T>(v->get())};
1252 }
1253 else
1254 {
1255 return {};
1256 }
1257 }
1258
1259 template <class T>
1260 typename std::enable_if<!std::is_integral<T>::value
1261 || std::is_same<T, bool>::value,
1262 option<T>>::type
get_impl(const std::shared_ptr<base> & elem)1263 get_impl(const std::shared_ptr<base>& elem)
1264 {
1265 if (auto v = elem->as<T>())
1266 {
1267 return {v->get()};
1268 }
1269 else
1270 {
1271 return {};
1272 }
1273 }
1274
1275 /**
1276 * Represents a TOML keytable.
1277 */
1278 class table : public base
1279 {
1280 public:
1281 friend class table_array;
1282 friend std::shared_ptr<table> make_table();
1283
1284 std::shared_ptr<base> clone() const override;
1285
1286 /**
1287 * tables can be iterated over.
1288 */
1289 using iterator = string_to_base_map::iterator;
1290
1291 /**
1292 * tables can be iterated over. Const version.
1293 */
1294 using const_iterator = string_to_base_map::const_iterator;
1295
begin()1296 iterator begin()
1297 {
1298 return map_.begin();
1299 }
1300
begin() const1301 const_iterator begin() const
1302 {
1303 return map_.begin();
1304 }
1305
end()1306 iterator end()
1307 {
1308 return map_.end();
1309 }
1310
end() const1311 const_iterator end() const
1312 {
1313 return map_.end();
1314 }
1315
is_table() const1316 bool is_table() const override
1317 {
1318 return true;
1319 }
1320
empty() const1321 bool empty() const
1322 {
1323 return map_.empty();
1324 }
1325
1326 /**
1327 * Determines if this key table contains the given key.
1328 */
contains(const std::string & key) const1329 bool contains(const std::string& key) const
1330 {
1331 return map_.find(key) != map_.end();
1332 }
1333
1334 /**
1335 * Determines if this key table contains the given key. Will
1336 * resolve "qualified keys". Qualified keys are the full access
1337 * path separated with dots like "grandparent.parent.child".
1338 */
contains_qualified(const std::string & key) const1339 bool contains_qualified(const std::string& key) const
1340 {
1341 return resolve_qualified(key);
1342 }
1343
1344 /**
1345 * Obtains the base for a given key.
1346 * @throw std::out_of_range if the key does not exist
1347 */
get(const std::string & key) const1348 std::shared_ptr<base> get(const std::string& key) const
1349 {
1350 return map_.at(key);
1351 }
1352
1353 /**
1354 * Obtains the base for a given key. Will resolve "qualified
1355 * keys". Qualified keys are the full access path separated with
1356 * dots like "grandparent.parent.child".
1357 *
1358 * @throw std::out_of_range if the key does not exist
1359 */
get_qualified(const std::string & key) const1360 std::shared_ptr<base> get_qualified(const std::string& key) const
1361 {
1362 std::shared_ptr<base> p;
1363 resolve_qualified(key, &p);
1364 return p;
1365 }
1366
1367 /**
1368 * Obtains a table for a given key, if possible.
1369 */
get_table(const std::string & key) const1370 std::shared_ptr<table> get_table(const std::string& key) const
1371 {
1372 if (contains(key) && get(key)->is_table())
1373 return std::static_pointer_cast<table>(get(key));
1374 return nullptr;
1375 }
1376
1377 /**
1378 * Obtains a table for a given key, if possible. Will resolve
1379 * "qualified keys".
1380 */
get_table_qualified(const std::string & key) const1381 std::shared_ptr<table> get_table_qualified(const std::string& key) const
1382 {
1383 if (contains_qualified(key) && get_qualified(key)->is_table())
1384 return std::static_pointer_cast<table>(get_qualified(key));
1385 return nullptr;
1386 }
1387
1388 /**
1389 * Obtains an array for a given key.
1390 */
get_array(const std::string & key) const1391 std::shared_ptr<array> get_array(const std::string& key) const
1392 {
1393 if (!contains(key))
1394 return nullptr;
1395 return get(key)->as_array();
1396 }
1397
1398 /**
1399 * Obtains an array for a given key. Will resolve "qualified keys".
1400 */
get_array_qualified(const std::string & key) const1401 std::shared_ptr<array> get_array_qualified(const std::string& key) const
1402 {
1403 if (!contains_qualified(key))
1404 return nullptr;
1405 return get_qualified(key)->as_array();
1406 }
1407
1408 /**
1409 * Obtains a table_array for a given key, if possible.
1410 */
get_table_array(const std::string & key) const1411 std::shared_ptr<table_array> get_table_array(const std::string& key) const
1412 {
1413 if (!contains(key))
1414 return nullptr;
1415 return get(key)->as_table_array();
1416 }
1417
1418 /**
1419 * Obtains a table_array for a given key, if possible. Will resolve
1420 * "qualified keys".
1421 */
1422 std::shared_ptr<table_array>
get_table_array_qualified(const std::string & key) const1423 get_table_array_qualified(const std::string& key) const
1424 {
1425 if (!contains_qualified(key))
1426 return nullptr;
1427 return get_qualified(key)->as_table_array();
1428 }
1429
1430 /**
1431 * Helper function that attempts to get a value corresponding
1432 * to the template parameter from a given key.
1433 */
1434 template <class T>
get_as(const std::string & key) const1435 option<T> get_as(const std::string& key) const
1436 {
1437 try
1438 {
1439 return get_impl<T>(get(key));
1440 }
1441 catch (const std::out_of_range&)
1442 {
1443 return {};
1444 }
1445 }
1446
1447 /**
1448 * Helper function that attempts to get a value corresponding
1449 * to the template parameter from a given key. Will resolve "qualified
1450 * keys".
1451 */
1452 template <class T>
get_qualified_as(const std::string & key) const1453 option<T> get_qualified_as(const std::string& key) const
1454 {
1455 try
1456 {
1457 return get_impl<T>(get_qualified(key));
1458 }
1459 catch (const std::out_of_range&)
1460 {
1461 return {};
1462 }
1463 }
1464
1465 /**
1466 * Helper function that attempts to get an array of values of a given
1467 * type corresponding to the template parameter for a given key.
1468 *
1469 * If the key doesn't exist, doesn't exist as an array type, or one or
1470 * more keys inside the array type are not of type T, an empty option
1471 * is returned. Otherwise, an option containing a vector of the values
1472 * is returned.
1473 */
1474 template <class T>
1475 inline typename array_of_trait<T>::return_type
get_array_of(const std::string & key) const1476 get_array_of(const std::string& key) const
1477 {
1478 if (auto v = get_array(key))
1479 {
1480 std::vector<T> result;
1481 result.reserve(v->get().size());
1482
1483 for (const auto& b : v->get())
1484 {
1485 if (auto val = b->as<T>())
1486 result.push_back(val->get());
1487 else
1488 return {};
1489 }
1490 return {std::move(result)};
1491 }
1492
1493 return {};
1494 }
1495
1496 /**
1497 * Helper function that attempts to get an array of values of a given
1498 * type corresponding to the template parameter for a given key. Will
1499 * resolve "qualified keys".
1500 *
1501 * If the key doesn't exist, doesn't exist as an array type, or one or
1502 * more keys inside the array type are not of type T, an empty option
1503 * is returned. Otherwise, an option containing a vector of the values
1504 * is returned.
1505 */
1506 template <class T>
1507 inline typename array_of_trait<T>::return_type
get_qualified_array_of(const std::string & key) const1508 get_qualified_array_of(const std::string& key) const
1509 {
1510 if (auto v = get_array_qualified(key))
1511 {
1512 std::vector<T> result;
1513 result.reserve(v->get().size());
1514
1515 for (const auto& b : v->get())
1516 {
1517 if (auto val = b->as<T>())
1518 result.push_back(val->get());
1519 else
1520 return {};
1521 }
1522 return {std::move(result)};
1523 }
1524
1525 return {};
1526 }
1527
1528 /**
1529 * Adds an element to the keytable.
1530 */
insert(const std::string & key,const std::shared_ptr<base> & value)1531 void insert(const std::string& key, const std::shared_ptr<base>& value)
1532 {
1533 map_[key] = value;
1534 }
1535
1536 /**
1537 * Convenience shorthand for adding a simple element to the
1538 * keytable.
1539 */
1540 template <class T>
insert(const std::string & key,T && val,typename value_traits<T>::type * =0)1541 void insert(const std::string& key, T&& val,
1542 typename value_traits<T>::type* = 0)
1543 {
1544 insert(key, make_value(std::forward<T>(val)));
1545 }
1546
1547 /**
1548 * Removes an element from the table.
1549 */
erase(const std::string & key)1550 void erase(const std::string& key)
1551 {
1552 map_.erase(key);
1553 }
1554
1555 private:
1556 #if defined(CPPTOML_NO_RTTI)
table()1557 table() : base(base_type::TABLE)
1558 {
1559 // nothing
1560 }
1561 #else
1562 table()
1563 {
1564 // nothing
1565 }
1566 #endif
1567
1568 table(const table& obj) = delete;
1569 table& operator=(const table& rhs) = delete;
1570
split(const std::string & value,char separator) const1571 std::vector<std::string> split(const std::string& value,
1572 char separator) const
1573 {
1574 std::vector<std::string> result;
1575 std::string::size_type p = 0;
1576 std::string::size_type q;
1577 while ((q = value.find(separator, p)) != std::string::npos)
1578 {
1579 result.emplace_back(value, p, q - p);
1580 p = q + 1;
1581 }
1582 result.emplace_back(value, p);
1583 return result;
1584 }
1585
1586 // If output parameter p is specified, fill it with the pointer to the
1587 // specified entry and throw std::out_of_range if it couldn't be found.
1588 //
1589 // Otherwise, just return true if the entry could be found or false
1590 // otherwise and do not throw.
resolve_qualified(const std::string & key,std::shared_ptr<base> * p=nullptr) const1591 bool resolve_qualified(const std::string& key,
1592 std::shared_ptr<base>* p = nullptr) const
1593 {
1594 auto parts = split(key, '.');
1595 auto last_key = parts.back();
1596 parts.pop_back();
1597
1598 auto cur_table = this;
1599 for (const auto& part : parts)
1600 {
1601 cur_table = cur_table->get_table(part).get();
1602 if (!cur_table)
1603 {
1604 if (!p)
1605 return false;
1606
1607 throw std::out_of_range{key + " is not a valid key"};
1608 }
1609 }
1610
1611 if (!p)
1612 return cur_table->map_.count(last_key) != 0;
1613
1614 *p = cur_table->map_.at(last_key);
1615 return true;
1616 }
1617
1618 string_to_base_map map_;
1619 };
1620
1621 /**
1622 * Helper function that attempts to get an array of arrays for a given
1623 * key.
1624 *
1625 * If the key doesn't exist, doesn't exist as an array type, or one or
1626 * more keys inside the array type are not of type T, an empty option
1627 * is returned. Otherwise, an option containing a vector of the values
1628 * is returned.
1629 */
1630 template <>
1631 inline typename array_of_trait<array>::return_type
get_array_of(const std::string & key) const1632 table::get_array_of<array>(const std::string& key) const
1633 {
1634 if (auto v = get_array(key))
1635 {
1636 std::vector<std::shared_ptr<array>> result;
1637 result.reserve(v->get().size());
1638
1639 for (const auto& b : v->get())
1640 {
1641 if (auto val = b->as_array())
1642 result.push_back(val);
1643 else
1644 return {};
1645 }
1646
1647 return {std::move(result)};
1648 }
1649
1650 return {};
1651 }
1652
1653 /**
1654 * Helper function that attempts to get an array of arrays for a given
1655 * key. Will resolve "qualified keys".
1656 *
1657 * If the key doesn't exist, doesn't exist as an array type, or one or
1658 * more keys inside the array type are not of type T, an empty option
1659 * is returned. Otherwise, an option containing a vector of the values
1660 * is returned.
1661 */
1662 template <>
1663 inline typename array_of_trait<array>::return_type
get_qualified_array_of(const std::string & key) const1664 table::get_qualified_array_of<array>(const std::string& key) const
1665 {
1666 if (auto v = get_array_qualified(key))
1667 {
1668 std::vector<std::shared_ptr<array>> result;
1669 result.reserve(v->get().size());
1670
1671 for (const auto& b : v->get())
1672 {
1673 if (auto val = b->as_array())
1674 result.push_back(val);
1675 else
1676 return {};
1677 }
1678
1679 return {std::move(result)};
1680 }
1681
1682 return {};
1683 }
1684
make_table()1685 std::shared_ptr<table> make_table()
1686 {
1687 struct make_shared_enabler : public table
1688 {
1689 make_shared_enabler()
1690 {
1691 // nothing
1692 }
1693 };
1694
1695 return std::make_shared<make_shared_enabler>();
1696 }
1697
1698 namespace detail
1699 {
1700 template <>
make_element()1701 inline std::shared_ptr<table> make_element<table>()
1702 {
1703 return make_table();
1704 }
1705 } // namespace detail
1706
1707 template <class T>
clone() const1708 std::shared_ptr<base> value<T>::clone() const
1709 {
1710 return make_value(data_);
1711 }
1712
clone() const1713 inline std::shared_ptr<base> array::clone() const
1714 {
1715 auto result = make_array();
1716 result->reserve(values_.size());
1717 for (const auto& ptr : values_)
1718 result->values_.push_back(ptr->clone());
1719 return result;
1720 }
1721
clone() const1722 inline std::shared_ptr<base> table_array::clone() const
1723 {
1724 auto result = make_table_array(is_inline());
1725 result->reserve(array_.size());
1726 for (const auto& ptr : array_)
1727 result->array_.push_back(ptr->clone()->as_table());
1728 return result;
1729 }
1730
clone() const1731 inline std::shared_ptr<base> table::clone() const
1732 {
1733 auto result = make_table();
1734 for (const auto& pr : map_)
1735 result->insert(pr.first, pr.second->clone());
1736 return result;
1737 }
1738
1739 /**
1740 * Exception class for all TOML parsing errors.
1741 */
1742 class parse_exception : public std::runtime_error
1743 {
1744 public:
parse_exception(const std::string & err)1745 parse_exception(const std::string& err) : std::runtime_error{err}
1746 {
1747 }
1748
parse_exception(const std::string & err,std::size_t line_number)1749 parse_exception(const std::string& err, std::size_t line_number)
1750 : std::runtime_error{err + " at line " + std::to_string(line_number)}
1751 {
1752 }
1753 };
1754
is_number(char c)1755 inline bool is_number(char c)
1756 {
1757 return c >= '0' && c <= '9';
1758 }
1759
is_hex(char c)1760 inline bool is_hex(char c)
1761 {
1762 return is_number(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
1763 }
1764
1765 /**
1766 * Helper object for consuming expected characters.
1767 */
1768 template <class OnError>
1769 class consumer
1770 {
1771 public:
consumer(std::string::iterator & it,const std::string::iterator & end,OnError && on_error)1772 consumer(std::string::iterator& it, const std::string::iterator& end,
1773 OnError&& on_error)
1774 : it_(it), end_(end), on_error_(std::forward<OnError>(on_error))
1775 {
1776 // nothing
1777 }
1778
operator ()(char c)1779 void operator()(char c)
1780 {
1781 if (it_ == end_ || *it_ != c)
1782 on_error_();
1783 ++it_;
1784 }
1785
1786 template <std::size_t N>
operator ()(const char (& str)[N])1787 void operator()(const char (&str)[N])
1788 {
1789 std::for_each(std::begin(str), std::end(str) - 1,
1790 [&](char c) { (*this)(c); });
1791 }
1792
eat_or(char a,char b)1793 void eat_or(char a, char b)
1794 {
1795 if (it_ == end_ || (*it_ != a && *it_ != b))
1796 on_error_();
1797 ++it_;
1798 }
1799
eat_digits(int len)1800 int eat_digits(int len)
1801 {
1802 int val = 0;
1803 for (int i = 0; i < len; ++i)
1804 {
1805 if (!is_number(*it_) || it_ == end_)
1806 on_error_();
1807 val = 10 * val + (*it_++ - '0');
1808 }
1809 return val;
1810 }
1811
error()1812 void error()
1813 {
1814 on_error_();
1815 }
1816
1817 private:
1818 std::string::iterator& it_;
1819 const std::string::iterator& end_;
1820 OnError on_error_;
1821 };
1822
1823 template <class OnError>
make_consumer(std::string::iterator & it,const std::string::iterator & end,OnError && on_error)1824 consumer<OnError> make_consumer(std::string::iterator& it,
1825 const std::string::iterator& end,
1826 OnError&& on_error)
1827 {
1828 return consumer<OnError>(it, end, std::forward<OnError>(on_error));
1829 }
1830
1831 // replacement for std::getline to handle incorrectly line-ended files
1832 // https://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf
1833 namespace detail
1834 {
getline(std::istream & input,std::string & line)1835 inline std::istream& getline(std::istream& input, std::string& line)
1836 {
1837 line.clear();
1838
1839 std::istream::sentry sentry{input, true};
1840 auto sb = input.rdbuf();
1841
1842 while (true)
1843 {
1844 auto c = sb->sbumpc();
1845 if (c == '\r')
1846 {
1847 if (sb->sgetc() == '\n')
1848 c = sb->sbumpc();
1849 }
1850
1851 if (c == '\n')
1852 return input;
1853
1854 if (c == std::istream::traits_type::eof())
1855 {
1856 if (line.empty())
1857 input.setstate(std::ios::eofbit);
1858 return input;
1859 }
1860
1861 line.push_back(static_cast<char>(c));
1862 }
1863 }
1864 } // namespace detail
1865
1866 /**
1867 * The parser class.
1868 */
1869 class parser
1870 {
1871 public:
1872 /**
1873 * Parsers are constructed from streams.
1874 */
parser(std::istream & stream)1875 parser(std::istream& stream) : input_(stream)
1876 {
1877 // nothing
1878 }
1879
1880 parser& operator=(const parser& parser) = delete;
1881
1882 /**
1883 * Parses the stream this parser was created on until EOF.
1884 * @throw parse_exception if there are errors in parsing
1885 */
parse()1886 std::shared_ptr<table> parse()
1887 {
1888 std::shared_ptr<table> root = make_table();
1889
1890 table* curr_table = root.get();
1891
1892 while (detail::getline(input_, line_))
1893 {
1894 line_number_++;
1895 auto it = line_.begin();
1896 auto end = line_.end();
1897 consume_whitespace(it, end);
1898 if (it == end || *it == '#')
1899 continue;
1900 if (*it == '[')
1901 {
1902 curr_table = root.get();
1903 parse_table(it, end, curr_table);
1904 }
1905 else
1906 {
1907 parse_key_value(it, end, curr_table);
1908 consume_whitespace(it, end);
1909 eol_or_comment(it, end);
1910 }
1911 }
1912 return root;
1913 }
1914
1915 private:
1916 #if defined _MSC_VER
1917 __declspec(noreturn)
1918 #elif defined __GNUC__
1919 __attribute__((noreturn))
1920 #endif
throw_parse_exception(const std::string & err)1921 void throw_parse_exception(const std::string& err)
1922 {
1923 throw parse_exception{err, line_number_};
1924 }
1925
parse_table(std::string::iterator & it,const std::string::iterator & end,table * & curr_table)1926 void parse_table(std::string::iterator& it,
1927 const std::string::iterator& end, table*& curr_table)
1928 {
1929 // remove the beginning keytable marker
1930 ++it;
1931 if (it == end)
1932 throw_parse_exception("Unexpected end of table");
1933 if (*it == '[')
1934 parse_table_array(it, end, curr_table);
1935 else
1936 parse_single_table(it, end, curr_table);
1937 }
1938
parse_single_table(std::string::iterator & it,const std::string::iterator & end,table * & curr_table)1939 void parse_single_table(std::string::iterator& it,
1940 const std::string::iterator& end,
1941 table*& curr_table)
1942 {
1943 if (it == end || *it == ']')
1944 throw_parse_exception("Table name cannot be empty");
1945
1946 std::string full_table_name;
1947 bool inserted = false;
1948
1949 auto key_end = [](char c) { return c == ']'; };
1950
1951 auto key_part_handler = [&](const std::string& part) {
1952 if (part.empty())
1953 throw_parse_exception("Empty component of table name");
1954
1955 if (!full_table_name.empty())
1956 full_table_name += '.';
1957 full_table_name += part;
1958
1959 if (curr_table->contains(part))
1960 {
1961 #if !defined(__PGI)
1962 auto b = curr_table->get(part);
1963 #else
1964 // Workaround for PGI compiler
1965 std::shared_ptr<base> b = curr_table->get(part);
1966 #endif
1967 if (b->is_table())
1968 curr_table = static_cast<table*>(b.get());
1969 else if (b->is_table_array())
1970 curr_table = std::static_pointer_cast<table_array>(b)
1971 ->get()
1972 .back()
1973 .get();
1974 else
1975 throw_parse_exception("Key " + full_table_name
1976 + "already exists as a value");
1977 }
1978 else
1979 {
1980 inserted = true;
1981 curr_table->insert(part, make_table());
1982 curr_table = static_cast<table*>(curr_table->get(part).get());
1983 }
1984 };
1985
1986 key_part_handler(parse_key(it, end, key_end, key_part_handler));
1987
1988 if (it == end)
1989 throw_parse_exception(
1990 "Unterminated table declaration; did you forget a ']'?");
1991
1992 if (*it != ']')
1993 {
1994 std::string errmsg{"Unexpected character in table definition: "};
1995 errmsg += '"';
1996 errmsg += *it;
1997 errmsg += '"';
1998 throw_parse_exception(errmsg);
1999 }
2000
2001 // table already existed
2002 if (!inserted)
2003 {
2004 auto is_value
2005 = [](const std::pair<const std::string&,
2006 const std::shared_ptr<base>&>& p) {
2007 return p.second->is_value();
2008 };
2009
2010 // if there are any values, we can't add values to this table
2011 // since it has already been defined. If there aren't any
2012 // values, then it was implicitly created by something like
2013 // [a.b]
2014 if (curr_table->empty()
2015 || std::any_of(curr_table->begin(), curr_table->end(),
2016 is_value))
2017 {
2018 throw_parse_exception("Redefinition of table "
2019 + full_table_name);
2020 }
2021 }
2022
2023 ++it;
2024 consume_whitespace(it, end);
2025 eol_or_comment(it, end);
2026 }
2027
parse_table_array(std::string::iterator & it,const std::string::iterator & end,table * & curr_table)2028 void parse_table_array(std::string::iterator& it,
2029 const std::string::iterator& end, table*& curr_table)
2030 {
2031 ++it;
2032 if (it == end || *it == ']')
2033 throw_parse_exception("Table array name cannot be empty");
2034
2035 auto key_end = [](char c) { return c == ']'; };
2036
2037 std::string full_ta_name;
2038 auto key_part_handler = [&](const std::string& part) {
2039 if (part.empty())
2040 throw_parse_exception("Empty component of table array name");
2041
2042 if (!full_ta_name.empty())
2043 full_ta_name += '.';
2044 full_ta_name += part;
2045
2046 if (curr_table->contains(part))
2047 {
2048 #if !defined(__PGI)
2049 auto b = curr_table->get(part);
2050 #else
2051 // Workaround for PGI compiler
2052 std::shared_ptr<base> b = curr_table->get(part);
2053 #endif
2054
2055 // if this is the end of the table array name, add an
2056 // element to the table array that we just looked up,
2057 // provided it was not declared inline
2058 if (it != end && *it == ']')
2059 {
2060 if (!b->is_table_array())
2061 {
2062 throw_parse_exception("Key " + full_ta_name
2063 + " is not a table array");
2064 }
2065
2066 auto v = b->as_table_array();
2067
2068 if (v->is_inline())
2069 {
2070 throw_parse_exception("Static array " + full_ta_name
2071 + " cannot be appended to");
2072 }
2073
2074 v->get().push_back(make_table());
2075 curr_table = v->get().back().get();
2076 }
2077 // otherwise, just keep traversing down the key name
2078 else
2079 {
2080 if (b->is_table())
2081 curr_table = static_cast<table*>(b.get());
2082 else if (b->is_table_array())
2083 curr_table = std::static_pointer_cast<table_array>(b)
2084 ->get()
2085 .back()
2086 .get();
2087 else
2088 throw_parse_exception("Key " + full_ta_name
2089 + " already exists as a value");
2090 }
2091 }
2092 else
2093 {
2094 // if this is the end of the table array name, add a new
2095 // table array and a new table inside that array for us to
2096 // add keys to next
2097 if (it != end && *it == ']')
2098 {
2099 curr_table->insert(part, make_table_array());
2100 auto arr = std::static_pointer_cast<table_array>(
2101 curr_table->get(part));
2102 arr->get().push_back(make_table());
2103 curr_table = arr->get().back().get();
2104 }
2105 // otherwise, create the implicitly defined table and move
2106 // down to it
2107 else
2108 {
2109 curr_table->insert(part, make_table());
2110 curr_table
2111 = static_cast<table*>(curr_table->get(part).get());
2112 }
2113 }
2114 };
2115
2116 key_part_handler(parse_key(it, end, key_end, key_part_handler));
2117
2118 // consume the last "]]"
2119 auto eat = make_consumer(it, end, [this]() {
2120 throw_parse_exception("Unterminated table array name");
2121 });
2122 eat(']');
2123 eat(']');
2124
2125 consume_whitespace(it, end);
2126 eol_or_comment(it, end);
2127 }
2128
parse_key_value(std::string::iterator & it,std::string::iterator & end,table * curr_table)2129 void parse_key_value(std::string::iterator& it, std::string::iterator& end,
2130 table* curr_table)
2131 {
2132 auto key_end = [](char c) { return c == '='; };
2133
2134 auto key_part_handler = [&](const std::string& part) {
2135 // two cases: this key part exists already, in which case it must
2136 // be a table, or it doesn't exist in which case we must create
2137 // an implicitly defined table
2138 if (curr_table->contains(part))
2139 {
2140 auto val = curr_table->get(part);
2141 if (val->is_table())
2142 {
2143 curr_table = static_cast<table*>(val.get());
2144 }
2145 else
2146 {
2147 throw_parse_exception("Key " + part
2148 + " already exists as a value");
2149 }
2150 }
2151 else
2152 {
2153 auto newtable = make_table();
2154 curr_table->insert(part, newtable);
2155 curr_table = newtable.get();
2156 }
2157 };
2158
2159 auto key = parse_key(it, end, key_end, key_part_handler);
2160
2161 if (curr_table->contains(key))
2162 throw_parse_exception("Key " + key + " already present");
2163 if (it == end || *it != '=')
2164 throw_parse_exception("Value must follow after a '='");
2165 ++it;
2166 consume_whitespace(it, end);
2167 curr_table->insert(key, parse_value(it, end));
2168 consume_whitespace(it, end);
2169 }
2170
2171 template <class KeyEndFinder, class KeyPartHandler>
2172 std::string
parse_key(std::string::iterator & it,const std::string::iterator & end,KeyEndFinder && key_end,KeyPartHandler && key_part_handler)2173 parse_key(std::string::iterator& it, const std::string::iterator& end,
2174 KeyEndFinder&& key_end, KeyPartHandler&& key_part_handler)
2175 {
2176 // parse the key as a series of one or more simple-keys joined with '.'
2177 while (it != end && !key_end(*it))
2178 {
2179 auto part = parse_simple_key(it, end);
2180 consume_whitespace(it, end);
2181
2182 if (it == end || key_end(*it))
2183 {
2184 return part;
2185 }
2186
2187 if (*it != '.')
2188 {
2189 std::string errmsg{"Unexpected character in key: "};
2190 errmsg += '"';
2191 errmsg += *it;
2192 errmsg += '"';
2193 throw_parse_exception(errmsg);
2194 }
2195
2196 key_part_handler(part);
2197
2198 // consume the dot
2199 ++it;
2200 }
2201
2202 throw_parse_exception("Unexpected end of key");
2203 }
2204
parse_simple_key(std::string::iterator & it,const std::string::iterator & end)2205 std::string parse_simple_key(std::string::iterator& it,
2206 const std::string::iterator& end)
2207 {
2208 consume_whitespace(it, end);
2209
2210 if (it == end)
2211 throw_parse_exception("Unexpected end of key (blank key?)");
2212
2213 if (*it == '"' || *it == '\'')
2214 {
2215 return string_literal(it, end, *it);
2216 }
2217 else
2218 {
2219 auto bke = std::find_if(it, end, [](char c) {
2220 return c == '.' || c == '=' || c == ']';
2221 });
2222 return parse_bare_key(it, bke);
2223 }
2224 }
2225
parse_bare_key(std::string::iterator & it,const std::string::iterator & end)2226 std::string parse_bare_key(std::string::iterator& it,
2227 const std::string::iterator& end)
2228 {
2229 if (it == end)
2230 {
2231 throw_parse_exception("Bare key missing name");
2232 }
2233
2234 auto key_end = end;
2235 --key_end;
2236 consume_backwards_whitespace(key_end, it);
2237 ++key_end;
2238 std::string key{it, key_end};
2239
2240 if (std::find(it, key_end, '#') != key_end)
2241 {
2242 throw_parse_exception("Bare key " + key + " cannot contain #");
2243 }
2244
2245 if (std::find_if(it, key_end,
2246 [](char c) { return c == ' ' || c == '\t'; })
2247 != key_end)
2248 {
2249 throw_parse_exception("Bare key " + key
2250 + " cannot contain whitespace");
2251 }
2252
2253 if (std::find_if(it, key_end,
2254 [](char c) { return c == '[' || c == ']'; })
2255 != key_end)
2256 {
2257 throw_parse_exception("Bare key " + key
2258 + " cannot contain '[' or ']'");
2259 }
2260
2261 it = end;
2262 return key;
2263 }
2264
2265 enum class parse_type
2266 {
2267 STRING = 1,
2268 LOCAL_TIME,
2269 LOCAL_DATE,
2270 LOCAL_DATETIME,
2271 OFFSET_DATETIME,
2272 INT,
2273 FLOAT,
2274 BOOL,
2275 ARRAY,
2276 INLINE_TABLE
2277 };
2278
parse_value(std::string::iterator & it,std::string::iterator & end)2279 std::shared_ptr<base> parse_value(std::string::iterator& it,
2280 std::string::iterator& end)
2281 {
2282 parse_type type = determine_value_type(it, end);
2283 switch (type)
2284 {
2285 case parse_type::STRING:
2286 return parse_string(it, end);
2287 case parse_type::LOCAL_TIME:
2288 return parse_time(it, end);
2289 case parse_type::LOCAL_DATE:
2290 case parse_type::LOCAL_DATETIME:
2291 case parse_type::OFFSET_DATETIME:
2292 return parse_date(it, end);
2293 case parse_type::INT:
2294 case parse_type::FLOAT:
2295 return parse_number(it, end);
2296 case parse_type::BOOL:
2297 return parse_bool(it, end);
2298 case parse_type::ARRAY:
2299 return parse_array(it, end);
2300 case parse_type::INLINE_TABLE:
2301 return parse_inline_table(it, end);
2302 default:
2303 throw_parse_exception("Failed to parse value");
2304 }
2305 }
2306
determine_value_type(const std::string::iterator & it,const std::string::iterator & end)2307 parse_type determine_value_type(const std::string::iterator& it,
2308 const std::string::iterator& end)
2309 {
2310 if (it == end)
2311 {
2312 throw_parse_exception("Failed to parse value type");
2313 }
2314 if (*it == '"' || *it == '\'')
2315 {
2316 return parse_type::STRING;
2317 }
2318 else if (is_time(it, end))
2319 {
2320 return parse_type::LOCAL_TIME;
2321 }
2322 else if (auto dtype = date_type(it, end))
2323 {
2324 return *dtype;
2325 }
2326 else if (is_number(*it) || *it == '-' || *it == '+'
2327 || (*it == 'i' && it + 1 != end && it[1] == 'n'
2328 && it + 2 != end && it[2] == 'f')
2329 || (*it == 'n' && it + 1 != end && it[1] == 'a'
2330 && it + 2 != end && it[2] == 'n'))
2331 {
2332 return determine_number_type(it, end);
2333 }
2334 else if (*it == 't' || *it == 'f')
2335 {
2336 return parse_type::BOOL;
2337 }
2338 else if (*it == '[')
2339 {
2340 return parse_type::ARRAY;
2341 }
2342 else if (*it == '{')
2343 {
2344 return parse_type::INLINE_TABLE;
2345 }
2346 throw_parse_exception("Failed to parse value type");
2347 }
2348
determine_number_type(const std::string::iterator & it,const std::string::iterator & end)2349 parse_type determine_number_type(const std::string::iterator& it,
2350 const std::string::iterator& end)
2351 {
2352 // determine if we are an integer or a float
2353 auto check_it = it;
2354 if (*check_it == '-' || *check_it == '+')
2355 ++check_it;
2356
2357 if (check_it == end)
2358 throw_parse_exception("Malformed number");
2359
2360 if (*check_it == 'i' || *check_it == 'n')
2361 return parse_type::FLOAT;
2362
2363 while (check_it != end && is_number(*check_it))
2364 ++check_it;
2365 if (check_it != end && *check_it == '.')
2366 {
2367 ++check_it;
2368 while (check_it != end && is_number(*check_it))
2369 ++check_it;
2370 return parse_type::FLOAT;
2371 }
2372 else
2373 {
2374 return parse_type::INT;
2375 }
2376 }
2377
parse_string(std::string::iterator & it,std::string::iterator & end)2378 std::shared_ptr<value<std::string>> parse_string(std::string::iterator& it,
2379 std::string::iterator& end)
2380 {
2381 auto delim = *it;
2382 assert(delim == '"' || delim == '\'');
2383
2384 // end is non-const here because we have to be able to potentially
2385 // parse multiple lines in a string, not just one
2386 auto check_it = it;
2387 ++check_it;
2388 if (check_it != end && *check_it == delim)
2389 {
2390 ++check_it;
2391 if (check_it != end && *check_it == delim)
2392 {
2393 it = ++check_it;
2394 return parse_multiline_string(it, end, delim);
2395 }
2396 }
2397 return make_value<std::string>(string_literal(it, end, delim));
2398 }
2399
2400 std::shared_ptr<value<std::string>>
parse_multiline_string(std::string::iterator & it,std::string::iterator & end,char delim)2401 parse_multiline_string(std::string::iterator& it,
2402 std::string::iterator& end, char delim)
2403 {
2404 std::stringstream ss;
2405
2406 auto is_ws = [](char c) { return c == ' ' || c == '\t'; };
2407
2408 bool consuming = false;
2409 std::shared_ptr<value<std::string>> ret;
2410
2411 auto handle_line = [&](std::string::iterator& local_it,
2412 std::string::iterator& local_end) {
2413 if (consuming)
2414 {
2415 local_it = std::find_if_not(local_it, local_end, is_ws);
2416
2417 // whole line is whitespace
2418 if (local_it == local_end)
2419 return;
2420 }
2421
2422 consuming = false;
2423
2424 while (local_it != local_end)
2425 {
2426 // handle escaped characters
2427 if (delim == '"' && *local_it == '\\')
2428 {
2429 auto check = local_it;
2430 // check if this is an actual escape sequence or a
2431 // whitespace escaping backslash
2432 ++check;
2433 consume_whitespace(check, local_end);
2434 if (check == local_end)
2435 {
2436 consuming = true;
2437 break;
2438 }
2439
2440 ss << parse_escape_code(local_it, local_end);
2441 continue;
2442 }
2443
2444 // if we can end the string
2445 if (std::distance(local_it, local_end) >= 3)
2446 {
2447 auto check = local_it;
2448 // check for """
2449 if (*check++ == delim && *check++ == delim
2450 && *check++ == delim)
2451 {
2452 local_it = check;
2453 ret = make_value<std::string>(ss.str());
2454 break;
2455 }
2456 }
2457
2458 ss << *local_it++;
2459 }
2460 };
2461
2462 // handle the remainder of the current line
2463 handle_line(it, end);
2464 if (ret)
2465 return ret;
2466
2467 // start eating lines
2468 while (detail::getline(input_, line_))
2469 {
2470 ++line_number_;
2471
2472 it = line_.begin();
2473 end = line_.end();
2474
2475 handle_line(it, end);
2476
2477 if (ret)
2478 return ret;
2479
2480 if (!consuming)
2481 ss << std::endl;
2482 }
2483
2484 throw_parse_exception("Unterminated multi-line basic string");
2485 }
2486
string_literal(std::string::iterator & it,const std::string::iterator & end,char delim)2487 std::string string_literal(std::string::iterator& it,
2488 const std::string::iterator& end, char delim)
2489 {
2490 ++it;
2491 std::string val;
2492 while (it != end)
2493 {
2494 // handle escaped characters
2495 if (delim == '"' && *it == '\\')
2496 {
2497 val += parse_escape_code(it, end);
2498 }
2499 else if (*it == delim)
2500 {
2501 ++it;
2502 consume_whitespace(it, end);
2503 return val;
2504 }
2505 else
2506 {
2507 val += *it++;
2508 }
2509 }
2510 throw_parse_exception("Unterminated string literal");
2511 }
2512
parse_escape_code(std::string::iterator & it,const std::string::iterator & end)2513 std::string parse_escape_code(std::string::iterator& it,
2514 const std::string::iterator& end)
2515 {
2516 ++it;
2517 if (it == end)
2518 throw_parse_exception("Invalid escape sequence");
2519 char value;
2520 if (*it == 'b')
2521 {
2522 value = '\b';
2523 }
2524 else if (*it == 't')
2525 {
2526 value = '\t';
2527 }
2528 else if (*it == 'n')
2529 {
2530 value = '\n';
2531 }
2532 else if (*it == 'f')
2533 {
2534 value = '\f';
2535 }
2536 else if (*it == 'r')
2537 {
2538 value = '\r';
2539 }
2540 else if (*it == '"')
2541 {
2542 value = '"';
2543 }
2544 else if (*it == '\\')
2545 {
2546 value = '\\';
2547 }
2548 else if (*it == 'u' || *it == 'U')
2549 {
2550 return parse_unicode(it, end);
2551 }
2552 else
2553 {
2554 throw_parse_exception("Invalid escape sequence");
2555 }
2556 ++it;
2557 return std::string(1, value);
2558 }
2559
parse_unicode(std::string::iterator & it,const std::string::iterator & end)2560 std::string parse_unicode(std::string::iterator& it,
2561 const std::string::iterator& end)
2562 {
2563 bool large = *it++ == 'U';
2564 auto codepoint = parse_hex(it, end, large ? 0x10000000 : 0x1000);
2565
2566 if ((codepoint > 0xd7ff && codepoint < 0xe000) || codepoint > 0x10ffff)
2567 {
2568 throw_parse_exception(
2569 "Unicode escape sequence is not a Unicode scalar value");
2570 }
2571
2572 std::string result;
2573 // See Table 3-6 of the Unicode standard
2574 if (codepoint <= 0x7f)
2575 {
2576 // 1-byte codepoints: 00000000 0xxxxxxx
2577 // repr: 0xxxxxxx
2578 result += static_cast<char>(codepoint & 0x7f);
2579 }
2580 else if (codepoint <= 0x7ff)
2581 {
2582 // 2-byte codepoints: 00000yyy yyxxxxxx
2583 // repr: 110yyyyy 10xxxxxx
2584 //
2585 // 0x1f = 00011111
2586 // 0xc0 = 11000000
2587 //
2588 result += static_cast<char>(0xc0 | ((codepoint >> 6) & 0x1f));
2589 //
2590 // 0x80 = 10000000
2591 // 0x3f = 00111111
2592 //
2593 result += static_cast<char>(0x80 | (codepoint & 0x3f));
2594 }
2595 else if (codepoint <= 0xffff)
2596 {
2597 // 3-byte codepoints: zzzzyyyy yyxxxxxx
2598 // repr: 1110zzzz 10yyyyyy 10xxxxxx
2599 //
2600 // 0xe0 = 11100000
2601 // 0x0f = 00001111
2602 //
2603 result += static_cast<char>(0xe0 | ((codepoint >> 12) & 0x0f));
2604 result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x1f));
2605 result += static_cast<char>(0x80 | (codepoint & 0x3f));
2606 }
2607 else
2608 {
2609 // 4-byte codepoints: 000uuuuu zzzzyyyy yyxxxxxx
2610 // repr: 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
2611 //
2612 // 0xf0 = 11110000
2613 // 0x07 = 00000111
2614 //
2615 result += static_cast<char>(0xf0 | ((codepoint >> 18) & 0x07));
2616 result += static_cast<char>(0x80 | ((codepoint >> 12) & 0x3f));
2617 result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f));
2618 result += static_cast<char>(0x80 | (codepoint & 0x3f));
2619 }
2620 return result;
2621 }
2622
parse_hex(std::string::iterator & it,const std::string::iterator & end,uint32_t place)2623 uint32_t parse_hex(std::string::iterator& it,
2624 const std::string::iterator& end, uint32_t place)
2625 {
2626 uint32_t value = 0;
2627 while (place > 0)
2628 {
2629 if (it == end)
2630 throw_parse_exception("Unexpected end of unicode sequence");
2631
2632 if (!is_hex(*it))
2633 throw_parse_exception("Invalid unicode escape sequence");
2634
2635 value += place * hex_to_digit(*it++);
2636 place /= 16;
2637 }
2638 return value;
2639 }
2640
hex_to_digit(char c)2641 uint32_t hex_to_digit(char c)
2642 {
2643 if (is_number(c))
2644 return static_cast<uint32_t>(c - '0');
2645 return 10
2646 + static_cast<uint32_t>(c
2647 - ((c >= 'a' && c <= 'f') ? 'a' : 'A'));
2648 }
2649
parse_number(std::string::iterator & it,const std::string::iterator & end)2650 std::shared_ptr<base> parse_number(std::string::iterator& it,
2651 const std::string::iterator& end)
2652 {
2653 auto check_it = it;
2654 auto check_end = find_end_of_number(it, end);
2655
2656 auto eat_sign = [&]() {
2657 if (check_it != end && (*check_it == '-' || *check_it == '+'))
2658 ++check_it;
2659 };
2660
2661 auto check_no_leading_zero = [&]() {
2662 if (check_it != end && *check_it == '0' && check_it + 1 != check_end
2663 && check_it[1] != '.')
2664 {
2665 throw_parse_exception("Numbers may not have leading zeros");
2666 }
2667 };
2668
2669 auto eat_digits = [&](bool (*check_char)(char)) {
2670 auto beg = check_it;
2671 while (check_it != end && check_char(*check_it))
2672 {
2673 ++check_it;
2674 if (check_it != end && *check_it == '_')
2675 {
2676 ++check_it;
2677 if (check_it == end || !check_char(*check_it))
2678 throw_parse_exception("Malformed number");
2679 }
2680 }
2681
2682 if (check_it == beg)
2683 throw_parse_exception("Malformed number");
2684 };
2685
2686 auto eat_hex = [&]() { eat_digits(&is_hex); };
2687
2688 auto eat_numbers = [&]() { eat_digits(&is_number); };
2689
2690 if (check_it != end && *check_it == '0' && check_it + 1 != check_end
2691 && (check_it[1] == 'x' || check_it[1] == 'o' || check_it[1] == 'b'))
2692 {
2693 ++check_it;
2694 char base = *check_it;
2695 ++check_it;
2696 if (base == 'x')
2697 {
2698 eat_hex();
2699 return parse_int(it, check_it, 16);
2700 }
2701 else if (base == 'o')
2702 {
2703 auto start = check_it;
2704 eat_numbers();
2705 auto val = parse_int(start, check_it, 8, "0");
2706 it = start;
2707 return val;
2708 }
2709 else // if (base == 'b')
2710 {
2711 auto start = check_it;
2712 eat_numbers();
2713 auto val = parse_int(start, check_it, 2);
2714 it = start;
2715 return val;
2716 }
2717 }
2718
2719 eat_sign();
2720 check_no_leading_zero();
2721
2722 if (check_it != end && check_it + 1 != end && check_it + 2 != end)
2723 {
2724 if (check_it[0] == 'i' && check_it[1] == 'n' && check_it[2] == 'f')
2725 {
2726 auto val = std::numeric_limits<double>::infinity();
2727 if (*it == '-')
2728 val = -val;
2729 it = check_it + 3;
2730 return make_value(val);
2731 }
2732 else if (check_it[0] == 'n' && check_it[1] == 'a'
2733 && check_it[2] == 'n')
2734 {
2735 auto val = std::numeric_limits<double>::quiet_NaN();
2736 if (*it == '-')
2737 val = -val;
2738 it = check_it + 3;
2739 return make_value(val);
2740 }
2741 }
2742
2743 eat_numbers();
2744
2745 if (check_it != end
2746 && (*check_it == '.' || *check_it == 'e' || *check_it == 'E'))
2747 {
2748 bool is_exp = *check_it == 'e' || *check_it == 'E';
2749
2750 ++check_it;
2751 if (check_it == end)
2752 throw_parse_exception("Floats must have trailing digits");
2753
2754 auto eat_exp = [&]() {
2755 eat_sign();
2756 check_no_leading_zero();
2757 eat_numbers();
2758 };
2759
2760 if (is_exp)
2761 eat_exp();
2762 else
2763 eat_numbers();
2764
2765 if (!is_exp && check_it != end
2766 && (*check_it == 'e' || *check_it == 'E'))
2767 {
2768 ++check_it;
2769 eat_exp();
2770 }
2771
2772 return parse_float(it, check_it);
2773 }
2774 else
2775 {
2776 return parse_int(it, check_it);
2777 }
2778 }
2779
parse_int(std::string::iterator & it,const std::string::iterator & end,int base=10,const char * prefix="")2780 std::shared_ptr<value<int64_t>> parse_int(std::string::iterator& it,
2781 const std::string::iterator& end,
2782 int base = 10,
2783 const char* prefix = "")
2784 {
2785 std::string v{it, end};
2786 v = prefix + v;
2787 v.erase(std::remove(v.begin(), v.end(), '_'), v.end());
2788 it = end;
2789 try
2790 {
2791 return make_value<int64_t>(std::stoll(v, nullptr, base));
2792 }
2793 catch (const std::invalid_argument& ex)
2794 {
2795 throw_parse_exception("Malformed number (invalid argument: "
2796 + std::string{ex.what()} + ")");
2797 }
2798 catch (const std::out_of_range& ex)
2799 {
2800 throw_parse_exception("Malformed number (out of range: "
2801 + std::string{ex.what()} + ")");
2802 }
2803 }
2804
parse_float(std::string::iterator & it,const std::string::iterator & end)2805 std::shared_ptr<value<double>> parse_float(std::string::iterator& it,
2806 const std::string::iterator& end)
2807 {
2808 std::string v{it, end};
2809 v.erase(std::remove(v.begin(), v.end(), '_'), v.end());
2810 it = end;
2811 char decimal_point = std::localeconv()->decimal_point[0];
2812 std::replace(v.begin(), v.end(), '.', decimal_point);
2813 try
2814 {
2815 return make_value<double>(std::stod(v));
2816 }
2817 catch (const std::invalid_argument& ex)
2818 {
2819 throw_parse_exception("Malformed number (invalid argument: "
2820 + std::string{ex.what()} + ")");
2821 }
2822 catch (const std::out_of_range& ex)
2823 {
2824 throw_parse_exception("Malformed number (out of range: "
2825 + std::string{ex.what()} + ")");
2826 }
2827 }
2828
parse_bool(std::string::iterator & it,const std::string::iterator & end)2829 std::shared_ptr<value<bool>> parse_bool(std::string::iterator& it,
2830 const std::string::iterator& end)
2831 {
2832 auto eat = make_consumer(it, end, [this]() {
2833 throw_parse_exception("Attempted to parse invalid boolean value");
2834 });
2835
2836 if (*it == 't')
2837 {
2838 eat("true");
2839 return make_value<bool>(true);
2840 }
2841 else if (*it == 'f')
2842 {
2843 eat("false");
2844 return make_value<bool>(false);
2845 }
2846
2847 eat.error();
2848 return nullptr;
2849 }
2850
find_end_of_number(std::string::iterator it,std::string::iterator end)2851 std::string::iterator find_end_of_number(std::string::iterator it,
2852 std::string::iterator end)
2853 {
2854 auto ret = std::find_if(it, end, [](char c) {
2855 return !is_number(c) && c != '_' && c != '.' && c != 'e' && c != 'E'
2856 && c != '-' && c != '+' && c != 'x' && c != 'o' && c != 'b';
2857 });
2858 if (ret != end && ret + 1 != end && ret + 2 != end)
2859 {
2860 if ((ret[0] == 'i' && ret[1] == 'n' && ret[2] == 'f')
2861 || (ret[0] == 'n' && ret[1] == 'a' && ret[2] == 'n'))
2862 {
2863 ret = ret + 3;
2864 }
2865 }
2866 return ret;
2867 }
2868
find_end_of_date(std::string::iterator it,std::string::iterator end)2869 std::string::iterator find_end_of_date(std::string::iterator it,
2870 std::string::iterator end)
2871 {
2872 auto end_of_date = std::find_if(it, end, [](char c) {
2873 return !is_number(c) && c != '-';
2874 });
2875 if (end_of_date != end && *end_of_date == ' ' && end_of_date + 1 != end
2876 && is_number(end_of_date[1]))
2877 end_of_date++;
2878 return std::find_if(end_of_date, end, [](char c) {
2879 return !is_number(c) && c != 'T' && c != 'Z' && c != ':'
2880 && c != '-' && c != '+' && c != '.';
2881 });
2882 }
2883
find_end_of_time(std::string::iterator it,std::string::iterator end)2884 std::string::iterator find_end_of_time(std::string::iterator it,
2885 std::string::iterator end)
2886 {
2887 return std::find_if(it, end, [](char c) {
2888 return !is_number(c) && c != ':' && c != '.';
2889 });
2890 }
2891
read_time(std::string::iterator & it,const std::string::iterator & end)2892 local_time read_time(std::string::iterator& it,
2893 const std::string::iterator& end)
2894 {
2895 auto time_end = find_end_of_time(it, end);
2896
2897 auto eat = make_consumer(
2898 it, time_end, [&]() { throw_parse_exception("Malformed time"); });
2899
2900 local_time ltime;
2901
2902 ltime.hour = eat.eat_digits(2);
2903 eat(':');
2904 ltime.minute = eat.eat_digits(2);
2905 eat(':');
2906 ltime.second = eat.eat_digits(2);
2907
2908 int power = 100000;
2909 if (it != time_end && *it == '.')
2910 {
2911 ++it;
2912 while (it != time_end && is_number(*it))
2913 {
2914 ltime.microsecond += power * (*it++ - '0');
2915 power /= 10;
2916 }
2917 }
2918
2919 if (it != time_end)
2920 throw_parse_exception("Malformed time");
2921
2922 return ltime;
2923 }
2924
2925 std::shared_ptr<value<local_time>>
parse_time(std::string::iterator & it,const std::string::iterator & end)2926 parse_time(std::string::iterator& it, const std::string::iterator& end)
2927 {
2928 return make_value(read_time(it, end));
2929 }
2930
parse_date(std::string::iterator & it,const std::string::iterator & end)2931 std::shared_ptr<base> parse_date(std::string::iterator& it,
2932 const std::string::iterator& end)
2933 {
2934 auto date_end = find_end_of_date(it, end);
2935
2936 auto eat = make_consumer(
2937 it, date_end, [&]() { throw_parse_exception("Malformed date"); });
2938
2939 local_date ldate;
2940 ldate.year = eat.eat_digits(4);
2941 eat('-');
2942 ldate.month = eat.eat_digits(2);
2943 eat('-');
2944 ldate.day = eat.eat_digits(2);
2945
2946 if (it == date_end)
2947 return make_value(ldate);
2948
2949 eat.eat_or('T', ' ');
2950
2951 local_datetime ldt;
2952 static_cast<local_date&>(ldt) = ldate;
2953 static_cast<local_time&>(ldt) = read_time(it, date_end);
2954
2955 if (it == date_end)
2956 return make_value(ldt);
2957
2958 offset_datetime dt;
2959 static_cast<local_datetime&>(dt) = ldt;
2960
2961 int hoff = 0;
2962 int moff = 0;
2963 if (*it == '+' || *it == '-')
2964 {
2965 auto plus = *it == '+';
2966 ++it;
2967
2968 hoff = eat.eat_digits(2);
2969 dt.hour_offset = (plus) ? hoff : -hoff;
2970 eat(':');
2971 moff = eat.eat_digits(2);
2972 dt.minute_offset = (plus) ? moff : -moff;
2973 }
2974 else if (*it == 'Z')
2975 {
2976 ++it;
2977 }
2978
2979 if (it != date_end)
2980 throw_parse_exception("Malformed date");
2981
2982 return make_value(dt);
2983 }
2984
parse_array(std::string::iterator & it,std::string::iterator & end)2985 std::shared_ptr<base> parse_array(std::string::iterator& it,
2986 std::string::iterator& end)
2987 {
2988 // this gets ugly because of the "homogeneity" restriction:
2989 // arrays can either be of only one type, or contain arrays
2990 // (each of those arrays could be of different types, though)
2991 //
2992 // because of the latter portion, we don't really have a choice
2993 // but to represent them as arrays of base values...
2994 ++it;
2995
2996 // ugh---have to read the first value to determine array type...
2997 skip_whitespace_and_comments(it, end);
2998
2999 // edge case---empty array
3000 if (*it == ']')
3001 {
3002 ++it;
3003 return make_array();
3004 }
3005
3006 auto val_end = std::find_if(
3007 it, end, [](char c) { return c == ',' || c == ']' || c == '#'; });
3008 parse_type type = determine_value_type(it, val_end);
3009 switch (type)
3010 {
3011 case parse_type::STRING:
3012 return parse_value_array<std::string>(it, end);
3013 case parse_type::LOCAL_TIME:
3014 return parse_value_array<local_time>(it, end);
3015 case parse_type::LOCAL_DATE:
3016 return parse_value_array<local_date>(it, end);
3017 case parse_type::LOCAL_DATETIME:
3018 return parse_value_array<local_datetime>(it, end);
3019 case parse_type::OFFSET_DATETIME:
3020 return parse_value_array<offset_datetime>(it, end);
3021 case parse_type::INT:
3022 return parse_value_array<int64_t>(it, end);
3023 case parse_type::FLOAT:
3024 return parse_value_array<double>(it, end);
3025 case parse_type::BOOL:
3026 return parse_value_array<bool>(it, end);
3027 case parse_type::ARRAY:
3028 return parse_object_array<array>(&parser::parse_array, '[', it,
3029 end);
3030 case parse_type::INLINE_TABLE:
3031 return parse_object_array<table_array>(
3032 &parser::parse_inline_table, '{', it, end);
3033 default:
3034 throw_parse_exception("Unable to parse array");
3035 }
3036 }
3037
3038 template <class Value>
parse_value_array(std::string::iterator & it,std::string::iterator & end)3039 std::shared_ptr<array> parse_value_array(std::string::iterator& it,
3040 std::string::iterator& end)
3041 {
3042 auto arr = make_array();
3043 while (it != end && *it != ']')
3044 {
3045 auto val = parse_value(it, end);
3046 if (auto v = val->as<Value>())
3047 arr->get().push_back(val);
3048 else
3049 throw_parse_exception("Arrays must be homogeneous");
3050 skip_whitespace_and_comments(it, end);
3051 if (*it != ',')
3052 break;
3053 ++it;
3054 skip_whitespace_and_comments(it, end);
3055 }
3056 if (it != end)
3057 ++it;
3058 return arr;
3059 }
3060
3061 template <class Object, class Function>
parse_object_array(Function && fun,char delim,std::string::iterator & it,std::string::iterator & end)3062 std::shared_ptr<Object> parse_object_array(Function&& fun, char delim,
3063 std::string::iterator& it,
3064 std::string::iterator& end)
3065 {
3066 auto arr = detail::make_element<Object>();
3067
3068 while (it != end && *it != ']')
3069 {
3070 if (*it != delim)
3071 throw_parse_exception("Unexpected character in array");
3072
3073 arr->get().push_back(((*this).*fun)(it, end));
3074 skip_whitespace_and_comments(it, end);
3075
3076 if (it == end || *it != ',')
3077 break;
3078
3079 ++it;
3080 skip_whitespace_and_comments(it, end);
3081 }
3082
3083 if (it == end || *it != ']')
3084 throw_parse_exception("Unterminated array");
3085
3086 ++it;
3087 return arr;
3088 }
3089
parse_inline_table(std::string::iterator & it,std::string::iterator & end)3090 std::shared_ptr<table> parse_inline_table(std::string::iterator& it,
3091 std::string::iterator& end)
3092 {
3093 auto tbl = make_table();
3094 do
3095 {
3096 ++it;
3097 if (it == end)
3098 throw_parse_exception("Unterminated inline table");
3099
3100 consume_whitespace(it, end);
3101 if (it != end && *it != '}')
3102 {
3103 parse_key_value(it, end, tbl.get());
3104 consume_whitespace(it, end);
3105 }
3106 } while (*it == ',');
3107
3108 if (it == end || *it != '}')
3109 throw_parse_exception("Unterminated inline table");
3110
3111 ++it;
3112 consume_whitespace(it, end);
3113
3114 return tbl;
3115 }
3116
skip_whitespace_and_comments(std::string::iterator & start,std::string::iterator & end)3117 void skip_whitespace_and_comments(std::string::iterator& start,
3118 std::string::iterator& end)
3119 {
3120 consume_whitespace(start, end);
3121 while (start == end || *start == '#')
3122 {
3123 if (!detail::getline(input_, line_))
3124 throw_parse_exception("Unclosed array");
3125 line_number_++;
3126 start = line_.begin();
3127 end = line_.end();
3128 consume_whitespace(start, end);
3129 }
3130 }
3131
consume_whitespace(std::string::iterator & it,const std::string::iterator & end)3132 void consume_whitespace(std::string::iterator& it,
3133 const std::string::iterator& end)
3134 {
3135 while (it != end && (*it == ' ' || *it == '\t'))
3136 ++it;
3137 }
3138
consume_backwards_whitespace(std::string::iterator & back,const std::string::iterator & front)3139 void consume_backwards_whitespace(std::string::iterator& back,
3140 const std::string::iterator& front)
3141 {
3142 while (back != front && (*back == ' ' || *back == '\t'))
3143 --back;
3144 }
3145
eol_or_comment(const std::string::iterator & it,const std::string::iterator & end)3146 void eol_or_comment(const std::string::iterator& it,
3147 const std::string::iterator& end)
3148 {
3149 if (it != end && *it != '#')
3150 throw_parse_exception("Unidentified trailing character '"
3151 + std::string{*it}
3152 + "'---did you forget a '#'?");
3153 }
3154
is_time(const std::string::iterator & it,const std::string::iterator & end)3155 bool is_time(const std::string::iterator& it,
3156 const std::string::iterator& end)
3157 {
3158 auto time_end = find_end_of_time(it, end);
3159 auto len = std::distance(it, time_end);
3160
3161 if (len < 8)
3162 return false;
3163
3164 if (it[2] != ':' || it[5] != ':')
3165 return false;
3166
3167 if (len > 8)
3168 return it[8] == '.' && len > 9;
3169
3170 return true;
3171 }
3172
date_type(const std::string::iterator & it,const std::string::iterator & end)3173 option<parse_type> date_type(const std::string::iterator& it,
3174 const std::string::iterator& end)
3175 {
3176 auto date_end = find_end_of_date(it, end);
3177 auto len = std::distance(it, date_end);
3178
3179 if (len < 10)
3180 return {};
3181
3182 if (it[4] != '-' || it[7] != '-')
3183 return {};
3184
3185 if (len >= 19 && (it[10] == 'T' || it[10] == ' ')
3186 && is_time(it + 11, date_end))
3187 {
3188 // datetime type
3189 auto time_end = find_end_of_time(it + 11, date_end);
3190 if (time_end == date_end)
3191 return {parse_type::LOCAL_DATETIME};
3192 else
3193 return {parse_type::OFFSET_DATETIME};
3194 }
3195 else if (len == 10)
3196 {
3197 // just a regular date
3198 return {parse_type::LOCAL_DATE};
3199 }
3200
3201 return {};
3202 }
3203
3204 std::istream& input_;
3205 std::string line_;
3206 std::size_t line_number_ = 0;
3207 };
3208
3209 /**
3210 * Utility function to parse a file as a TOML file. Returns the root table.
3211 * Throws a parse_exception if the file cannot be opened.
3212 */
parse_file(const std::string & filename)3213 inline std::shared_ptr<table> parse_file(const std::string& filename)
3214 {
3215 #if defined(BOOST_NOWIDE_FSTREAM_INCLUDED_HPP)
3216 boost::nowide::ifstream file{filename.c_str()};
3217 #elif defined(NOWIDE_FSTREAM_INCLUDED_HPP)
3218 nowide::ifstream file{filename.c_str()};
3219 #else
3220 std::ifstream file{filename};
3221 #endif
3222 if (!file.is_open())
3223 throw parse_exception{filename + " could not be opened for parsing"};
3224 parser p{file};
3225 return p.parse();
3226 }
3227
3228 template <class... Ts>
3229 struct value_accept;
3230
3231 template <>
3232 struct value_accept<>
3233 {
3234 template <class Visitor, class... Args>
acceptcpptoml::value_accept3235 static void accept(const base&, Visitor&&, Args&&...)
3236 {
3237 // nothing
3238 }
3239 };
3240
3241 template <class T, class... Ts>
3242 struct value_accept<T, Ts...>
3243 {
3244 template <class Visitor, class... Args>
acceptcpptoml::value_accept3245 static void accept(const base& b, Visitor&& visitor, Args&&... args)
3246 {
3247 if (auto v = b.as<T>())
3248 {
3249 visitor.visit(*v, std::forward<Args>(args)...);
3250 }
3251 else
3252 {
3253 value_accept<Ts...>::accept(b, std::forward<Visitor>(visitor),
3254 std::forward<Args>(args)...);
3255 }
3256 }
3257 };
3258
3259 /**
3260 * base implementation of accept() that calls visitor.visit() on the concrete
3261 * class.
3262 */
3263 template <class Visitor, class... Args>
accept(Visitor && visitor,Args &&...args) const3264 void base::accept(Visitor&& visitor, Args&&... args) const
3265 {
3266 if (is_value())
3267 {
3268 using value_acceptor
3269 = value_accept<std::string, int64_t, double, bool, local_date,
3270 local_time, local_datetime, offset_datetime>;
3271 value_acceptor::accept(*this, std::forward<Visitor>(visitor),
3272 std::forward<Args>(args)...);
3273 }
3274 else if (is_table())
3275 {
3276 visitor.visit(static_cast<const table&>(*this),
3277 std::forward<Args>(args)...);
3278 }
3279 else if (is_array())
3280 {
3281 visitor.visit(static_cast<const array&>(*this),
3282 std::forward<Args>(args)...);
3283 }
3284 else if (is_table_array())
3285 {
3286 visitor.visit(static_cast<const table_array&>(*this),
3287 std::forward<Args>(args)...);
3288 }
3289 }
3290
3291 /**
3292 * Writer that can be passed to accept() functions of cpptoml objects and
3293 * will output valid TOML to a stream.
3294 */
3295 class toml_writer
3296 {
3297 public:
3298 /**
3299 * Construct a toml_writer that will write to the given stream
3300 */
toml_writer(std::ostream & s,const std::string & indent_space="\\t")3301 toml_writer(std::ostream& s, const std::string& indent_space = "\t")
3302 : stream_(s), indent_(indent_space), has_naked_endline_(false)
3303 {
3304 // nothing
3305 }
3306
3307 public:
3308 /**
3309 * Output a base value of the TOML tree.
3310 */
3311 template <class T>
visit(const value<T> & v,bool=false)3312 void visit(const value<T>& v, bool = false)
3313 {
3314 write(v);
3315 }
3316
3317 /**
3318 * Output a table element of the TOML tree
3319 */
visit(const table & t,bool in_array=false)3320 void visit(const table& t, bool in_array = false)
3321 {
3322 write_table_header(in_array);
3323 std::vector<std::string> values;
3324 std::vector<std::string> tables;
3325
3326 for (const auto& i : t)
3327 {
3328 if (i.second->is_table() || i.second->is_table_array())
3329 {
3330 tables.push_back(i.first);
3331 }
3332 else
3333 {
3334 values.push_back(i.first);
3335 }
3336 }
3337
3338 for (unsigned int i = 0; i < values.size(); ++i)
3339 {
3340 path_.push_back(values[i]);
3341
3342 if (i > 0)
3343 endline();
3344
3345 write_table_item_header(*t.get(values[i]));
3346 t.get(values[i])->accept(*this, false);
3347 path_.pop_back();
3348 }
3349
3350 for (unsigned int i = 0; i < tables.size(); ++i)
3351 {
3352 path_.push_back(tables[i]);
3353
3354 if (values.size() > 0 || i > 0)
3355 endline();
3356
3357 write_table_item_header(*t.get(tables[i]));
3358 t.get(tables[i])->accept(*this, false);
3359 path_.pop_back();
3360 }
3361
3362 endline();
3363 }
3364
3365 /**
3366 * Output an array element of the TOML tree
3367 */
visit(const array & a,bool=false)3368 void visit(const array& a, bool = false)
3369 {
3370 write("[");
3371
3372 for (unsigned int i = 0; i < a.get().size(); ++i)
3373 {
3374 if (i > 0)
3375 write(", ");
3376
3377 if (a.get()[i]->is_array())
3378 {
3379 a.get()[i]->as_array()->accept(*this, true);
3380 }
3381 else
3382 {
3383 a.get()[i]->accept(*this, true);
3384 }
3385 }
3386
3387 write("]");
3388 }
3389
3390 /**
3391 * Output a table_array element of the TOML tree
3392 */
visit(const table_array & t,bool=false)3393 void visit(const table_array& t, bool = false)
3394 {
3395 for (unsigned int j = 0; j < t.get().size(); ++j)
3396 {
3397 if (j > 0)
3398 endline();
3399
3400 t.get()[j]->accept(*this, true);
3401 }
3402
3403 endline();
3404 }
3405
3406 /**
3407 * Escape a string for output.
3408 */
escape_string(const std::string & str)3409 static std::string escape_string(const std::string& str)
3410 {
3411 std::string res;
3412 for (auto it = str.begin(); it != str.end(); ++it)
3413 {
3414 if (*it == '\b')
3415 {
3416 res += "\\b";
3417 }
3418 else if (*it == '\t')
3419 {
3420 res += "\\t";
3421 }
3422 else if (*it == '\n')
3423 {
3424 res += "\\n";
3425 }
3426 else if (*it == '\f')
3427 {
3428 res += "\\f";
3429 }
3430 else if (*it == '\r')
3431 {
3432 res += "\\r";
3433 }
3434 else if (*it == '"')
3435 {
3436 res += "\\\"";
3437 }
3438 else if (*it == '\\')
3439 {
3440 res += "\\\\";
3441 }
3442 else if (static_cast<uint32_t>(*it) <= UINT32_C(0x001f))
3443 {
3444 res += "\\u";
3445 std::stringstream ss;
3446 ss << std::hex << static_cast<uint32_t>(*it);
3447 res += ss.str();
3448 }
3449 else
3450 {
3451 res += *it;
3452 }
3453 }
3454 return res;
3455 }
3456
3457 protected:
3458 /**
3459 * Write out a string.
3460 */
write(const value<std::string> & v)3461 void write(const value<std::string>& v)
3462 {
3463 write("\"");
3464 write(escape_string(v.get()));
3465 write("\"");
3466 }
3467
3468 /**
3469 * Write out a double.
3470 */
write(const value<double> & v)3471 void write(const value<double>& v)
3472 {
3473 std::stringstream ss;
3474 ss << std::showpoint
3475 << std::setprecision(std::numeric_limits<double>::max_digits10)
3476 << v.get();
3477
3478 auto double_str = ss.str();
3479 auto pos = double_str.find("e0");
3480 if (pos != std::string::npos)
3481 double_str.replace(pos, 2, "e");
3482 pos = double_str.find("e-0");
3483 if (pos != std::string::npos)
3484 double_str.replace(pos, 3, "e-");
3485
3486 stream_ << double_str;
3487 has_naked_endline_ = false;
3488 }
3489
3490 /**
3491 * Write out an integer, local_date, local_time, local_datetime, or
3492 * offset_datetime.
3493 */
3494 template <class T>
3495 typename std::enable_if<
3496 is_one_of<T, int64_t, local_date, local_time, local_datetime,
3497 offset_datetime>::value>::type
write(const value<T> & v)3498 write(const value<T>& v)
3499 {
3500 write(v.get());
3501 }
3502
3503 /**
3504 * Write out a boolean.
3505 */
write(const value<bool> & v)3506 void write(const value<bool>& v)
3507 {
3508 write((v.get() ? "true" : "false"));
3509 }
3510
3511 /**
3512 * Write out the header of a table.
3513 */
write_table_header(bool in_array=false)3514 void write_table_header(bool in_array = false)
3515 {
3516 if (!path_.empty())
3517 {
3518 indent();
3519
3520 write("[");
3521
3522 if (in_array)
3523 {
3524 write("[");
3525 }
3526
3527 for (unsigned int i = 0; i < path_.size(); ++i)
3528 {
3529 if (i > 0)
3530 {
3531 write(".");
3532 }
3533
3534 if (path_[i].find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcde"
3535 "fghijklmnopqrstuvwxyz0123456789"
3536 "_-")
3537 == std::string::npos)
3538 {
3539 write(path_[i]);
3540 }
3541 else
3542 {
3543 write("\"");
3544 write(escape_string(path_[i]));
3545 write("\"");
3546 }
3547 }
3548
3549 if (in_array)
3550 {
3551 write("]");
3552 }
3553
3554 write("]");
3555 endline();
3556 }
3557 }
3558
3559 /**
3560 * Write out the identifier for an item in a table.
3561 */
write_table_item_header(const base & b)3562 void write_table_item_header(const base& b)
3563 {
3564 if (!b.is_table() && !b.is_table_array())
3565 {
3566 indent();
3567
3568 if (path_.back().find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcde"
3569 "fghijklmnopqrstuvwxyz0123456789"
3570 "_-")
3571 == std::string::npos)
3572 {
3573 write(path_.back());
3574 }
3575 else
3576 {
3577 write("\"");
3578 write(escape_string(path_.back()));
3579 write("\"");
3580 }
3581
3582 write(" = ");
3583 }
3584 }
3585
3586 private:
3587 /**
3588 * Indent the proper number of tabs given the size of
3589 * the path.
3590 */
indent()3591 void indent()
3592 {
3593 for (std::size_t i = 1; i < path_.size(); ++i)
3594 write(indent_);
3595 }
3596
3597 /**
3598 * Write a value out to the stream.
3599 */
3600 template <class T>
write(const T & v)3601 void write(const T& v)
3602 {
3603 stream_ << v;
3604 has_naked_endline_ = false;
3605 }
3606
3607 /**
3608 * Write an endline out to the stream
3609 */
endline()3610 void endline()
3611 {
3612 if (!has_naked_endline_)
3613 {
3614 stream_ << "\n";
3615 has_naked_endline_ = true;
3616 }
3617 }
3618
3619 private:
3620 std::ostream& stream_;
3621 const std::string indent_;
3622 std::vector<std::string> path_;
3623 bool has_naked_endline_;
3624 };
3625
operator <<(std::ostream & stream,const base & b)3626 inline std::ostream& operator<<(std::ostream& stream, const base& b)
3627 {
3628 toml_writer writer{stream};
3629 b.accept(writer);
3630 return stream;
3631 }
3632
3633 template <class T>
operator <<(std::ostream & stream,const value<T> & v)3634 std::ostream& operator<<(std::ostream& stream, const value<T>& v)
3635 {
3636 toml_writer writer{stream};
3637 v.accept(writer);
3638 return stream;
3639 }
3640
operator <<(std::ostream & stream,const table & t)3641 inline std::ostream& operator<<(std::ostream& stream, const table& t)
3642 {
3643 toml_writer writer{stream};
3644 t.accept(writer);
3645 return stream;
3646 }
3647
operator <<(std::ostream & stream,const table_array & t)3648 inline std::ostream& operator<<(std::ostream& stream, const table_array& t)
3649 {
3650 toml_writer writer{stream};
3651 t.accept(writer);
3652 return stream;
3653 }
3654
operator <<(std::ostream & stream,const array & a)3655 inline std::ostream& operator<<(std::ostream& stream, const array& a)
3656 {
3657 toml_writer writer{stream};
3658 a.accept(writer);
3659 return stream;
3660 }
3661 } // namespace cpptoml
3662 #endif // CPPTOML_H
3663