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