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