cpptoml
A C++11 parser for TOML
cpptoml.h
Go to the documentation of this file.
1 
7 #ifndef _CPPTOML_H_
8 #define _CPPTOML_H_
9 
10 #include <algorithm>
11 #include <cassert>
12 #include <cstdint>
13 #include <cstring>
14 #include <fstream>
15 #include <iomanip>
16 #include <map>
17 #include <memory>
18 #include <sstream>
19 #include <stdexcept>
20 #include <string>
21 #include <unordered_map>
22 #include <vector>
23 
24 #if __cplusplus > 201103L
25 #define CPPTOML_DEPRECATED(reason) [[deprecated(reason)]]
26 #elif defined(__clang__)
27 #define CPPTOML_DEPRECATED(reason) __attribute__((deprecated(reason)))
28 #elif defined(__GNUG__)
29 #define CPPTOML_DEPRECATED(reason) __attribute__((deprecated))
30 #elif defined(_MSC_VER)
31 #if _MSC_VER < 1910
32 #define CPPTOML_DEPRECATED(reason) __declspec(deprecated)
33 #else
34 #define CPPTOML_DEPRECATED(reason) [[deprecated(reason)]]
35 #endif
36 #endif
37 
38 namespace cpptoml
39 {
40 class writer; // forward declaration
41 class base; // forward declaration
42 #if defined(CPPTOML_USE_MAP)
43 // a std::map will ensure that entries a sorted, albeit at a slight
44 // performance penalty relative to the (default) unordered_map
45 using string_to_base_map = std::map<std::string, std::shared_ptr<base>>;
46 #else
47 // by default an unordered_map is used for best performance as the
48 // toml specification does not require entries to be sorted
49 using string_to_base_map
50  = std::unordered_map<std::string, std::shared_ptr<base>>;
51 #endif
52 
53 template <class T>
54 class option
55 {
56  public:
57  option() : empty_{true}
58  {
59  // nothing
60  }
61 
62  option(T value) : empty_{false}, value_{std::move(value)}
63  {
64  // nothing
65  }
66 
67  explicit operator bool() const
68  {
69  return !empty_;
70  }
71 
72  const T& operator*() const
73  {
74  return value_;
75  }
76 
77  const T* operator->() const
78  {
79  return &value_;
80  }
81 
82  const T& value_or(const T& alternative) const
83  {
84  if (!empty_)
85  return value_;
86  return 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 
102 {
103  int hour = 0;
104  int minute = 0;
105  int second = 0;
106  int microsecond = 0;
107 };
108 
110 {
111  int hour_offset = 0;
112  int minute_offset = 0;
113 };
114 
116 {
117 };
118 
120 {
121  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")
141  static inline struct offset_datetime from_local(const struct tm& t)
142  {
143  return from_zoned(t);
144  }
145 
146  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 
163 {
164  public:
165  fill_guard(std::ostream& os) : os_(os), fill_{os.fill()}
166  {
167  // nothing
168  }
169 
170  ~fill_guard()
171  {
172  os_.fill(fill_);
173  }
174 
175  private:
176  std::ostream& os_;
177  std::ostream::char_type fill_;
178 };
179 
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 
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 
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 
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 
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>
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>
283 
284 template <class T>
286 {
287 
289  || std::is_convertible<T, std::string>::value;
290 };
291 
292 template <class T>
293 struct value_traits<T, typename std::
294  enable_if<valid_value_or_string_convertible<T>::
295  value>::type>
296 {
297  using value_type = typename std::
298  conditional<valid_value<typename std::decay<T>::type>::value,
299  typename std::decay<T>::type, std::string>::type;
300 
301  using type = value<value_type>;
302 
303  static value_type construct(T&& val)
304  {
305  return value_type(val);
306  }
307 };
308 
309 template <class T>
310 struct value_traits<T,
311  typename std::
312  enable_if<!valid_value_or_string_convertible<T>::value
313  && std::is_floating_point<
314  typename std::decay<T>::type>::value>::
315  type>
316 {
317  using value_type = typename std::decay<T>::type;
318 
319  using type = value<double>;
320 
321  static value_type construct(T&& val)
322  {
323  return value_type(val);
324  }
325 };
326 
327 template <class T>
328 struct value_traits<T,
329  typename std::
330  enable_if<!valid_value_or_string_convertible<T>::value
331  && std::is_signed<typename std::decay<T>::
332  type>::value>::type>
333 {
334  using value_type = int64_t;
335 
336  using type = value<int64_t>;
337 
338  static value_type construct(T&& val)
339  {
340  if (val < std::numeric_limits<int64_t>::min())
341  throw std::underflow_error{"constructed value cannot be "
342  "represented by a 64-bit signed "
343  "integer"};
344 
345  if (val > std::numeric_limits<int64_t>::max())
346  throw std::overflow_error{"constructed value cannot be represented "
347  "by a 64-bit signed integer"};
348 
349  return static_cast<int64_t>(val);
350  }
351 };
352 
353 template <class T>
354 struct value_traits<T,
355  typename std::
356  enable_if<!valid_value_or_string_convertible<T>::value
357  && std::is_unsigned<typename std::decay<T>::
358  type>::value>::type>
359 {
360  using value_type = int64_t;
361 
362  using type = value<int64_t>;
363 
364  static value_type construct(T&& val)
365  {
366  if (val > static_cast<uint64_t>(std::numeric_limits<int64_t>::max()))
367  throw std::overflow_error{"constructed value cannot be represented "
368  "by a 64-bit signed integer"};
369 
370  return static_cast<int64_t>(val);
371  }
372 };
373 
374 class array;
375 class table;
376 class table_array;
377 
378 template <class T>
380 {
382 };
383 
384 template <>
386 {
388 };
389 
390 template <class T>
391 inline std::shared_ptr<typename value_traits<T>::type> make_value(T&& val);
392 inline std::shared_ptr<array> make_array();
393 template <class T>
394 inline std::shared_ptr<T> make_element();
395 inline std::shared_ptr<table> make_table();
396 inline std::shared_ptr<table_array> make_table_array();
397 
401 class base : public std::enable_shared_from_this<base>
402 {
403  public:
404  virtual ~base() = default;
405 
406  virtual std::shared_ptr<base> clone() const = 0;
407 
411  virtual bool is_value() const
412  {
413  return false;
414  }
415 
419  virtual bool is_table() const
420  {
421  return false;
422  }
423 
427  std::shared_ptr<table> as_table()
428  {
429  if (is_table())
430  return std::static_pointer_cast<table>(shared_from_this());
431  return nullptr;
432  }
436  virtual bool is_array() const
437  {
438  return false;
439  }
440 
444  std::shared_ptr<array> as_array()
445  {
446  if (is_array())
447  return std::static_pointer_cast<array>(shared_from_this());
448  return nullptr;
449  }
450 
454  virtual bool is_table_array() const
455  {
456  return false;
457  }
458 
462  std::shared_ptr<table_array> as_table_array()
463  {
464  if (is_table_array())
465  return std::static_pointer_cast<table_array>(shared_from_this());
466  return nullptr;
467  }
468 
473  template <class T>
474  std::shared_ptr<value<T>> as();
475 
476  template <class T>
477  std::shared_ptr<const value<T>> as() const;
478 
479  template <class Visitor, class... Args>
480  void accept(Visitor&& visitor, Args&&... args) const;
481 
482  protected:
483  base()
484  {
485  // nothing
486  }
487 };
488 
492 template <class T>
493 class value : public base
494 {
496  {
497  // nothing; this is a private key accessible only to friends
498  };
499 
500  template <class U>
501  friend std::shared_ptr<typename value_traits<U>::type>
502  cpptoml::make_value(U&& val);
503 
504  public:
505  static_assert(valid_value<T>::value, "invalid value type");
506 
507  std::shared_ptr<base> clone() const override;
508 
509  value(const make_shared_enabler&, const T& val) : value(val)
510  {
511  // nothing; note that users cannot actually invoke this function
512  // because they lack access to the make_shared_enabler.
513  }
514 
515  bool is_value() const override
516  {
517  return true;
518  }
519 
523  T& get()
524  {
525  return data_;
526  }
527 
531  const T& get() const
532  {
533  return data_;
534  }
535 
536  private:
537  T data_;
538 
542  value(const T& val) : data_(val)
543  {
544  }
545 
546  value(const value& val) = delete;
547  value& operator=(const value& val) = delete;
548 };
549 
550 template <class T>
551 std::shared_ptr<typename value_traits<T>::type> make_value(T&& val)
552 {
553  using value_type = typename value_traits<T>::type;
554  using enabler = typename value_type::make_shared_enabler;
555  return std::make_shared<value_type>(
556  enabler{}, value_traits<T>::construct(std::forward<T>(val)));
557 }
558 
559 template <class T>
560 inline std::shared_ptr<value<T>> base::as()
561 {
562  return std::dynamic_pointer_cast<value<T>>(shared_from_this());
563 }
564 
565 // special case value<double> to allow getting an integer parameter as a
566 // double value
567 template <>
568 inline std::shared_ptr<value<double>> base::as()
569 {
570  if (auto v = std::dynamic_pointer_cast<value<double>>(shared_from_this()))
571  return v;
572 
573  if (auto v = std::dynamic_pointer_cast<value<int64_t>>(shared_from_this()))
574  return make_value<double>(static_cast<double>(v->get()));
575 
576  return nullptr;
577 }
578 
579 template <class T>
580 inline std::shared_ptr<const value<T>> base::as() const
581 {
582  return std::dynamic_pointer_cast<const value<T>>(shared_from_this());
583 }
584 
585 // special case value<double> to allow getting an integer parameter as a
586 // double value
587 template <>
588 inline std::shared_ptr<const value<double>> base::as() const
589 {
590  if (auto v
591  = std::dynamic_pointer_cast<const value<double>>(shared_from_this()))
592  return v;
593 
594  if (auto v = as<int64_t>())
595  {
596  // the below has to be a non-const value<double> due to a bug in
597  // libc++: https://llvm.org/bugs/show_bug.cgi?id=18843
598  return make_value<double>(static_cast<double>(v->get()));
599  }
600 
601  return nullptr;
602 }
603 
607 class array_exception : public std::runtime_error
608 {
609  public:
610  array_exception(const std::string& err) : std::runtime_error{err}
611  {
612  }
613 };
614 
615 class array : public base
616 {
617  public:
618  friend std::shared_ptr<array> make_array();
619 
620  std::shared_ptr<base> clone() const override;
621 
622  virtual bool is_array() const override
623  {
624  return true;
625  }
626 
627  using size_type = std::size_t;
628 
632  using iterator = std::vector<std::shared_ptr<base>>::iterator;
633 
637  using const_iterator = std::vector<std::shared_ptr<base>>::const_iterator;
638 
639  iterator begin()
640  {
641  return values_.begin();
642  }
643 
644  const_iterator begin() const
645  {
646  return values_.begin();
647  }
648 
649  iterator end()
650  {
651  return values_.end();
652  }
653 
654  const_iterator end() const
655  {
656  return values_.end();
657  }
658 
662  std::vector<std::shared_ptr<base>>& get()
663  {
664  return values_;
665  }
666 
670  const std::vector<std::shared_ptr<base>>& get() const
671  {
672  return values_;
673  }
674 
675  std::shared_ptr<base> at(size_t idx) const
676  {
677  return values_.at(idx);
678  }
679 
684  template <class T>
685  std::vector<std::shared_ptr<value<T>>> array_of() const
686  {
687  std::vector<std::shared_ptr<value<T>>> result(values_.size());
688 
689  std::transform(values_.begin(), values_.end(), result.begin(),
690  [&](std::shared_ptr<base> v) { return v->as<T>(); });
691 
692  return result;
693  }
694 
699  template <class T>
701  {
702  std::vector<T> result;
703  result.reserve(values_.size());
704 
705  for (const auto& val : values_)
706  {
707  if (auto v = val->as<T>())
708  result.push_back(v->get());
709  else
710  return {};
711  }
712 
713  return {std::move(result)};
714  }
715 
720  std::vector<std::shared_ptr<array>> nested_array() const
721  {
722  std::vector<std::shared_ptr<array>> result(values_.size());
723 
724  std::transform(values_.begin(), values_.end(), result.begin(),
725  [&](std::shared_ptr<base> v) -> std::shared_ptr<array> {
726  if (v->is_array())
727  return std::static_pointer_cast<array>(v);
728  return std::shared_ptr<array>{};
729  });
730 
731  return result;
732  }
733 
737  template <class T>
738  void push_back(const std::shared_ptr<value<T>>& val)
739  {
740  if (values_.empty() || values_[0]->as<T>())
741  {
742  values_.push_back(val);
743  }
744  else
745  {
746  throw array_exception{"Arrays must be homogenous."};
747  }
748  }
749 
753  void push_back(const std::shared_ptr<array>& val)
754  {
755  if (values_.empty() || values_[0]->is_array())
756  {
757  values_.push_back(val);
758  }
759  else
760  {
761  throw array_exception{"Arrays must be homogenous."};
762  }
763  }
764 
769  template <class T>
770  void push_back(T&& val, typename value_traits<T>::type* = 0)
771  {
772  push_back(make_value(std::forward<T>(val)));
773  }
774 
778  template <class T>
779  iterator insert(iterator position, const std::shared_ptr<value<T>>& value)
780  {
781  if (values_.empty() || values_[0]->as<T>())
782  {
783  return values_.insert(position, value);
784  }
785  else
786  {
787  throw array_exception{"Arrays must be homogenous."};
788  }
789  }
790 
794  iterator insert(iterator position, const std::shared_ptr<array>& value)
795  {
796  if (values_.empty() || values_[0]->is_array())
797  {
798  return values_.insert(position, value);
799  }
800  else
801  {
802  throw array_exception{"Arrays must be homogenous."};
803  }
804  }
805 
809  template <class T>
810  iterator insert(iterator position, T&& val,
811  typename value_traits<T>::type* = 0)
812  {
813  return insert(position, make_value(std::forward<T>(val)));
814  }
815 
820  {
821  return values_.erase(position);
822  }
823 
827  void clear()
828  {
829  values_.clear();
830  }
831 
835  void reserve(size_type n)
836  {
837  values_.reserve(n);
838  }
839 
840  private:
841  array() = default;
842 
843  template <class InputIterator>
844  array(InputIterator begin, InputIterator end) : values_{begin, end}
845  {
846  // nothing
847  }
848 
849  array(const array& obj) = delete;
850  array& operator=(const array& obj) = delete;
851 
852  std::vector<std::shared_ptr<base>> values_;
853 };
854 
855 inline std::shared_ptr<array> make_array()
856 {
857  struct make_shared_enabler : public array
858  {
859  make_shared_enabler()
860  {
861  // nothing
862  }
863  };
864 
865  return std::make_shared<make_shared_enabler>();
866 }
867 
868 template <>
869 inline std::shared_ptr<array> make_element<array>()
870 {
871  return make_array();
872 }
873 
878 template <>
879 inline typename array_of_trait<array>::return_type
880 array::get_array_of<array>() const
881 {
882  std::vector<std::shared_ptr<array>> result;
883  result.reserve(values_.size());
884 
885  for (const auto& val : values_)
886  {
887  if (auto v = val->as_array())
888  result.push_back(v);
889  else
890  return {};
891  }
892 
893  return {std::move(result)};
894 }
895 
896 class table;
897 
898 class table_array : public base
899 {
900  friend class table;
901  friend std::shared_ptr<table_array> make_table_array();
902 
903  public:
904  std::shared_ptr<base> clone() const override;
905 
906  using size_type = std::size_t;
907 
911  using iterator = std::vector<std::shared_ptr<table>>::iterator;
912 
916  using const_iterator = std::vector<std::shared_ptr<table>>::const_iterator;
917 
918  iterator begin()
919  {
920  return array_.begin();
921  }
922 
923  const_iterator begin() const
924  {
925  return array_.begin();
926  }
927 
928  iterator end()
929  {
930  return array_.end();
931  }
932 
933  const_iterator end() const
934  {
935  return array_.end();
936  }
937 
938  virtual bool is_table_array() const override
939  {
940  return true;
941  }
942 
943  std::vector<std::shared_ptr<table>>& get()
944  {
945  return array_;
946  }
947 
948  const std::vector<std::shared_ptr<table>>& get() const
949  {
950  return array_;
951  }
952 
956  void push_back(const std::shared_ptr<table>& val)
957  {
958  array_.push_back(val);
959  }
960 
964  iterator insert(iterator position, const std::shared_ptr<table>& value)
965  {
966  return array_.insert(position, value);
967  }
968 
973  {
974  return array_.erase(position);
975  }
976 
980  void clear()
981  {
982  array_.clear();
983  }
984 
988  void reserve(size_type n)
989  {
990  array_.reserve(n);
991  }
992 
993  private:
994  table_array()
995  {
996  // nothing
997  }
998 
999  table_array(const table_array& obj) = delete;
1000  table_array& operator=(const table_array& rhs) = delete;
1001 
1002  std::vector<std::shared_ptr<table>> array_;
1003 };
1004 
1005 inline std::shared_ptr<table_array> make_table_array()
1006 {
1007  struct make_shared_enabler : public table_array
1008  {
1009  make_shared_enabler()
1010  {
1011  // nothing
1012  }
1013  };
1014 
1015  return std::make_shared<make_shared_enabler>();
1016 }
1017 
1018 template <>
1019 inline std::shared_ptr<table_array> make_element<table_array>()
1020 {
1021  return make_table_array();
1022 }
1023 
1024 // The below are overloads for fetching specific value types out of a value
1025 // where special casting behavior (like bounds checking) is desired
1026 
1027 template <class T>
1028 typename std::enable_if<!std::is_floating_point<T>::value
1029  && std::is_signed<T>::value,
1030  option<T>>::type
1031 get_impl(const std::shared_ptr<base>& elem)
1032 {
1033  if (auto v = elem->as<int64_t>())
1034  {
1035  if (v->get() < std::numeric_limits<T>::min())
1036  throw std::underflow_error{
1037  "T cannot represent the value requested in get"};
1038 
1039  if (v->get() > std::numeric_limits<T>::max())
1040  throw std::overflow_error{
1041  "T cannot represent the value requested in get"};
1042 
1043  return {static_cast<T>(v->get())};
1044  }
1045  else
1046  {
1047  return {};
1048  }
1049 }
1050 
1051 template <class T>
1052 typename std::enable_if<!std::is_same<T, bool>::value
1053  && std::is_unsigned<T>::value,
1054  option<T>>::type
1055 get_impl(const std::shared_ptr<base>& elem)
1056 {
1057  if (auto v = elem->as<int64_t>())
1058  {
1059  if (v->get() < 0)
1060  throw std::underflow_error{"T cannot store negative value in get"};
1061 
1062  if (static_cast<uint64_t>(v->get()) > std::numeric_limits<T>::max())
1063  throw std::overflow_error{
1064  "T cannot represent the value requested in get"};
1065 
1066  return {static_cast<T>(v->get())};
1067  }
1068  else
1069  {
1070  return {};
1071  }
1072 }
1073 
1074 template <class T>
1075 typename std::enable_if<!std::is_integral<T>::value
1076  || std::is_same<T, bool>::value,
1077  option<T>>::type
1078 get_impl(const std::shared_ptr<base>& elem)
1079 {
1080  if (auto v = elem->as<T>())
1081  {
1082  return {v->get()};
1083  }
1084  else
1085  {
1086  return {};
1087  }
1088 }
1089 
1093 class table : public base
1094 {
1095  public:
1096  friend class table_array;
1097  friend std::shared_ptr<table> make_table();
1098 
1099  std::shared_ptr<base> clone() const override;
1100 
1104  using iterator = string_to_base_map::iterator;
1105 
1109  using const_iterator = string_to_base_map::const_iterator;
1110 
1111  iterator begin()
1112  {
1113  return map_.begin();
1114  }
1115 
1116  const_iterator begin() const
1117  {
1118  return map_.begin();
1119  }
1120 
1121  iterator end()
1122  {
1123  return map_.end();
1124  }
1125 
1126  const_iterator end() const
1127  {
1128  return map_.end();
1129  }
1130 
1131  bool is_table() const override
1132  {
1133  return true;
1134  }
1135 
1136  bool empty() const
1137  {
1138  return map_.empty();
1139  }
1140 
1144  bool contains(const std::string& key) const
1145  {
1146  return map_.find(key) != map_.end();
1147  }
1148 
1154  bool contains_qualified(const std::string& key) const
1155  {
1156  return resolve_qualified(key);
1157  }
1158 
1163  std::shared_ptr<base> get(const std::string& key) const
1164  {
1165  return map_.at(key);
1166  }
1167 
1175  std::shared_ptr<base> get_qualified(const std::string& key) const
1176  {
1177  std::shared_ptr<base> p;
1178  resolve_qualified(key, &p);
1179  return p;
1180  }
1181 
1185  std::shared_ptr<table> get_table(const std::string& key) const
1186  {
1187  if (contains(key) && get(key)->is_table())
1188  return std::static_pointer_cast<table>(get(key));
1189  return nullptr;
1190  }
1191 
1196  std::shared_ptr<table> get_table_qualified(const std::string& key) const
1197  {
1198  if (contains_qualified(key) && get_qualified(key)->is_table())
1199  return std::static_pointer_cast<table>(get_qualified(key));
1200  return nullptr;
1201  }
1202 
1206  std::shared_ptr<array> get_array(const std::string& key) const
1207  {
1208  if (!contains(key))
1209  return nullptr;
1210  return get(key)->as_array();
1211  }
1212 
1216  std::shared_ptr<array> get_array_qualified(const std::string& key) const
1217  {
1218  if (!contains_qualified(key))
1219  return nullptr;
1220  return get_qualified(key)->as_array();
1221  }
1222 
1226  std::shared_ptr<table_array> get_table_array(const std::string& key) const
1227  {
1228  if (!contains(key))
1229  return nullptr;
1230  return get(key)->as_table_array();
1231  }
1232 
1237  std::shared_ptr<table_array>
1238  get_table_array_qualified(const std::string& key) const
1239  {
1240  if (!contains_qualified(key))
1241  return nullptr;
1242  return get_qualified(key)->as_table_array();
1243  }
1244 
1249  template <class T>
1250  option<T> get_as(const std::string& key) const
1251  {
1252  try
1253  {
1254  return get_impl<T>(get(key));
1255  }
1256  catch (const std::out_of_range&)
1257  {
1258  return {};
1259  }
1260  }
1261 
1267  template <class T>
1268  option<T> get_qualified_as(const std::string& key) const
1269  {
1270  try
1271  {
1272  return get_impl<T>(get_qualified(key));
1273  }
1274  catch (const std::out_of_range&)
1275  {
1276  return {};
1277  }
1278  }
1279 
1289  template <class T>
1290  inline typename array_of_trait<T>::return_type
1291  get_array_of(const std::string& key) const
1292  {
1293  if (auto v = get_array(key))
1294  {
1295  std::vector<T> result;
1296  result.reserve(v->get().size());
1297 
1298  for (const auto& b : v->get())
1299  {
1300  if (auto val = b->as<T>())
1301  result.push_back(val->get());
1302  else
1303  return {};
1304  }
1305  return {std::move(result)};
1306  }
1307 
1308  return {};
1309  }
1310 
1321  template <class T>
1322  inline typename array_of_trait<T>::return_type
1323  get_qualified_array_of(const std::string& key) const
1324  {
1325  if (auto v = get_array_qualified(key))
1326  {
1327  std::vector<T> result;
1328  result.reserve(v->get().size());
1329 
1330  for (const auto& b : v->get())
1331  {
1332  if (auto val = b->as<T>())
1333  result.push_back(val->get());
1334  else
1335  return {};
1336  }
1337  return {std::move(result)};
1338  }
1339 
1340  return {};
1341  }
1342 
1346  void insert(const std::string& key, const std::shared_ptr<base>& value)
1347  {
1348  map_[key] = value;
1349  }
1350 
1355  template <class T>
1356  void insert(const std::string& key, T&& val,
1357  typename value_traits<T>::type* = 0)
1358  {
1359  insert(key, make_value(std::forward<T>(val)));
1360  }
1361 
1365  void erase(const std::string& key)
1366  {
1367  map_.erase(key);
1368  }
1369 
1370  private:
1371  table()
1372  {
1373  // nothing
1374  }
1375 
1376  table(const table& obj) = delete;
1377  table& operator=(const table& rhs) = delete;
1378 
1379  std::vector<std::string> split(const std::string& value,
1380  char separator) const
1381  {
1382  std::vector<std::string> result;
1383  std::string::size_type p = 0;
1384  std::string::size_type q;
1385  while ((q = value.find(separator, p)) != std::string::npos)
1386  {
1387  result.emplace_back(value, p, q - p);
1388  p = q + 1;
1389  }
1390  result.emplace_back(value, p);
1391  return result;
1392  }
1393 
1394  // If output parameter p is specified, fill it with the pointer to the
1395  // specified entry and throw std::out_of_range if it couldn't be found.
1396  //
1397  // Otherwise, just return true if the entry could be found or false
1398  // otherwise and do not throw.
1399  bool resolve_qualified(const std::string& key,
1400  std::shared_ptr<base>* p = nullptr) const
1401  {
1402  auto parts = split(key, '.');
1403  auto last_key = parts.back();
1404  parts.pop_back();
1405 
1406  auto table = this;
1407  for (const auto& part : parts)
1408  {
1409  table = table->get_table(part).get();
1410  if (!table)
1411  {
1412  if (!p)
1413  return false;
1414 
1415  throw std::out_of_range{key + " is not a valid key"};
1416  }
1417  }
1418 
1419  if (!p)
1420  return table->map_.count(last_key) != 0;
1421 
1422  *p = table->map_.at(last_key);
1423  return true;
1424  }
1425 
1426  string_to_base_map map_;
1427 };
1428 
1438 template <>
1439 inline typename array_of_trait<array>::return_type
1440 table::get_array_of<array>(const std::string& key) const
1441 {
1442  if (auto v = get_array(key))
1443  {
1444  std::vector<std::shared_ptr<array>> result;
1445  result.reserve(v->get().size());
1446 
1447  for (const auto& b : v->get())
1448  {
1449  if (auto val = b->as_array())
1450  result.push_back(val);
1451  else
1452  return {};
1453  }
1454 
1455  return {std::move(result)};
1456  }
1457 
1458  return {};
1459 }
1460 
1470 template <>
1471 inline typename array_of_trait<array>::return_type
1472 table::get_qualified_array_of<array>(const std::string& key) const
1473 {
1474  if (auto v = get_array_qualified(key))
1475  {
1476  std::vector<std::shared_ptr<array>> result;
1477  result.reserve(v->get().size());
1478 
1479  for (const auto& b : v->get())
1480  {
1481  if (auto val = b->as_array())
1482  result.push_back(val);
1483  else
1484  return {};
1485  }
1486 
1487  return {std::move(result)};
1488  }
1489 
1490  return {};
1491 }
1492 
1493 std::shared_ptr<table> make_table()
1494 {
1495  struct make_shared_enabler : public table
1496  {
1497  make_shared_enabler()
1498  {
1499  // nothing
1500  }
1501  };
1502 
1503  return std::make_shared<make_shared_enabler>();
1504 }
1505 
1506 template <>
1507 inline std::shared_ptr<table> make_element<table>()
1508 {
1509  return make_table();
1510 }
1511 
1512 template <class T>
1513 std::shared_ptr<base> value<T>::clone() const
1514 {
1515  return make_value(data_);
1516 }
1517 
1518 inline std::shared_ptr<base> array::clone() const
1519 {
1520  auto result = make_array();
1521  result->reserve(values_.size());
1522  for (const auto& ptr : values_)
1523  result->values_.push_back(ptr->clone());
1524  return result;
1525 }
1526 
1527 inline std::shared_ptr<base> table_array::clone() const
1528 {
1529  auto result = make_table_array();
1530  result->reserve(array_.size());
1531  for (const auto& ptr : array_)
1532  result->array_.push_back(ptr->clone()->as_table());
1533  return result;
1534 }
1535 
1536 inline std::shared_ptr<base> table::clone() const
1537 {
1538  auto result = make_table();
1539  for (const auto& pr : map_)
1540  result->insert(pr.first, pr.second->clone());
1541  return result;
1542 }
1543 
1547 class parse_exception : public std::runtime_error
1548 {
1549  public:
1550  parse_exception(const std::string& err) : std::runtime_error{err}
1551  {
1552  }
1553 
1554  parse_exception(const std::string& err, std::size_t line_number)
1555  : std::runtime_error{err + " at line " + std::to_string(line_number)}
1556  {
1557  }
1558 };
1559 
1560 inline bool is_number(char c)
1561 {
1562  return c >= '0' && c <= '9';
1563 }
1564 
1568 template <class OnError>
1570 {
1571  public:
1572  consumer(std::string::iterator& it, const std::string::iterator& end,
1573  OnError&& on_error)
1574  : it_(it), end_(end), on_error_(std::forward<OnError>(on_error))
1575  {
1576  // nothing
1577  }
1578 
1579  void operator()(char c)
1580  {
1581  if (it_ == end_ || *it_ != c)
1582  on_error_();
1583  ++it_;
1584  }
1585 
1586  template <std::size_t N>
1587  void operator()(const char (&str)[N])
1588  {
1589  std::for_each(std::begin(str), std::end(str) - 1,
1590  [&](char c) { (*this)(c); });
1591  }
1592 
1593  int eat_digits(int len)
1594  {
1595  int val = 0;
1596  for (int i = 0; i < len; ++i)
1597  {
1598  if (!is_number(*it_) || it_ == end_)
1599  on_error_();
1600  val = 10 * val + (*it_++ - '0');
1601  }
1602  return val;
1603  }
1604 
1605  void error()
1606  {
1607  on_error_();
1608  }
1609 
1610  private:
1611  std::string::iterator& it_;
1612  const std::string::iterator& end_;
1613  OnError on_error_;
1614 };
1615 
1616 template <class OnError>
1617 consumer<OnError> make_consumer(std::string::iterator& it,
1618  const std::string::iterator& end,
1619  OnError&& on_error)
1620 {
1621  return consumer<OnError>(it, end, std::forward<OnError>(on_error));
1622 }
1623 
1624 // replacement for std::getline to handle incorrectly line-ended files
1625 // https://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf
1626 namespace detail
1627 {
1628 inline std::istream& getline(std::istream& input, std::string& line)
1629 {
1630  line.clear();
1631 
1632  std::istream::sentry sentry{input, true};
1633  auto sb = input.rdbuf();
1634 
1635  while (true)
1636  {
1637  auto c = sb->sbumpc();
1638  if (c == '\r')
1639  {
1640  if (sb->sgetc() == '\n')
1641  c = sb->sbumpc();
1642  }
1643 
1644  if (c == '\n')
1645  return input;
1646 
1647  if (c == std::istream::traits_type::eof())
1648  {
1649  if (line.empty())
1650  input.setstate(std::ios::eofbit);
1651  return input;
1652  }
1653 
1654  line.push_back(static_cast<char>(c));
1655  }
1656 }
1657 }
1658 
1662 class parser
1663 {
1664  public:
1668  parser(std::istream& stream) : input_(stream)
1669  {
1670  // nothing
1671  }
1672 
1673  parser& operator=(const parser& parser) = delete;
1674 
1679  std::shared_ptr<table> parse()
1680  {
1681  std::shared_ptr<table> root = make_table();
1682 
1683  table* curr_table = root.get();
1684 
1685  while (detail::getline(input_, line_))
1686  {
1687  line_number_++;
1688  auto it = line_.begin();
1689  auto end = line_.end();
1690  consume_whitespace(it, end);
1691  if (it == end || *it == '#')
1692  continue;
1693  if (*it == '[')
1694  {
1695  curr_table = root.get();
1696  parse_table(it, end, curr_table);
1697  }
1698  else
1699  {
1700  parse_key_value(it, end, curr_table);
1701  consume_whitespace(it, end);
1702  eol_or_comment(it, end);
1703  }
1704  }
1705  return root;
1706  }
1707 
1708  private:
1709 #if defined _MSC_VER
1710  __declspec(noreturn)
1711 #elif defined __GNUC__
1712  __attribute__((noreturn))
1713 #endif
1714  void throw_parse_exception(const std::string& err)
1715  {
1716  throw parse_exception{err, line_number_};
1717  }
1718 
1719  void parse_table(std::string::iterator& it,
1720  const std::string::iterator& end, table*& curr_table)
1721  {
1722  // remove the beginning keytable marker
1723  ++it;
1724  if (it == end)
1725  throw_parse_exception("Unexpected end of table");
1726  if (*it == '[')
1727  parse_table_array(it, end, curr_table);
1728  else
1729  parse_single_table(it, end, curr_table);
1730  }
1731 
1732  void parse_single_table(std::string::iterator& it,
1733  const std::string::iterator& end,
1734  table*& curr_table)
1735  {
1736  if (it == end || *it == ']')
1737  throw_parse_exception("Table name cannot be empty");
1738 
1739  std::string full_table_name;
1740  bool inserted = false;
1741  while (it != end && *it != ']')
1742  {
1743  auto part = parse_key(it, end,
1744  [](char c) { return c == '.' || c == ']'; });
1745 
1746  if (part.empty())
1747  throw_parse_exception("Empty component of table name");
1748 
1749  if (!full_table_name.empty())
1750  full_table_name += ".";
1751  full_table_name += part;
1752 
1753  if (curr_table->contains(part))
1754  {
1755  auto b = curr_table->get(part);
1756  if (b->is_table())
1757  curr_table = static_cast<table*>(b.get());
1758  else if (b->is_table_array())
1759  curr_table = std::static_pointer_cast<table_array>(b)
1760  ->get()
1761  .back()
1762  .get();
1763  else
1764  throw_parse_exception("Key " + full_table_name
1765  + "already exists as a value");
1766  }
1767  else
1768  {
1769  inserted = true;
1770  curr_table->insert(part, make_table());
1771  curr_table = static_cast<table*>(curr_table->get(part).get());
1772  }
1773  consume_whitespace(it, end);
1774  if (it != end && *it == '.')
1775  ++it;
1776  consume_whitespace(it, end);
1777  }
1778 
1779  if (it == end)
1780  throw_parse_exception(
1781  "Unterminated table declaration; did you forget a ']'?");
1782 
1783  // table already existed
1784  if (!inserted)
1785  {
1786  auto is_value
1787  = [](const std::pair<const std::string&,
1788  const std::shared_ptr<base>&>& p) {
1789  return p.second->is_value();
1790  };
1791 
1792  // if there are any values, we can't add values to this table
1793  // since it has already been defined. If there aren't any
1794  // values, then it was implicitly created by something like
1795  // [a.b]
1796  if (curr_table->empty() || std::any_of(curr_table->begin(),
1797  curr_table->end(), is_value))
1798  {
1799  throw_parse_exception("Redefinition of table "
1800  + full_table_name);
1801  }
1802  }
1803 
1804  ++it;
1805  consume_whitespace(it, end);
1806  eol_or_comment(it, end);
1807  }
1808 
1809  void parse_table_array(std::string::iterator& it,
1810  const std::string::iterator& end, table*& curr_table)
1811  {
1812  ++it;
1813  if (it == end || *it == ']')
1814  throw_parse_exception("Table array name cannot be empty");
1815 
1816  std::string full_ta_name;
1817  while (it != end && *it != ']')
1818  {
1819  auto part = parse_key(it, end,
1820  [](char c) { return c == '.' || c == ']'; });
1821 
1822  if (part.empty())
1823  throw_parse_exception("Empty component of table array name");
1824 
1825  if (!full_ta_name.empty())
1826  full_ta_name += ".";
1827  full_ta_name += part;
1828 
1829  consume_whitespace(it, end);
1830  if (it != end && *it == '.')
1831  ++it;
1832  consume_whitespace(it, end);
1833 
1834  if (curr_table->contains(part))
1835  {
1836  auto b = curr_table->get(part);
1837 
1838  // if this is the end of the table array name, add an
1839  // element to the table array that we just looked up
1840  if (it != end && *it == ']')
1841  {
1842  if (!b->is_table_array())
1843  throw_parse_exception("Key " + full_ta_name
1844  + " is not a table array");
1845  auto v = b->as_table_array();
1846  v->get().push_back(make_table());
1847  curr_table = v->get().back().get();
1848  }
1849  // otherwise, just keep traversing down the key name
1850  else
1851  {
1852  if (b->is_table())
1853  curr_table = static_cast<table*>(b.get());
1854  else if (b->is_table_array())
1855  curr_table = std::static_pointer_cast<table_array>(b)
1856  ->get()
1857  .back()
1858  .get();
1859  else
1860  throw_parse_exception("Key " + full_ta_name
1861  + " already exists as a value");
1862  }
1863  }
1864  else
1865  {
1866  // if this is the end of the table array name, add a new
1867  // table array and a new table inside that array for us to
1868  // add keys to next
1869  if (it != end && *it == ']')
1870  {
1871  curr_table->insert(part, make_table_array());
1872  auto arr = std::static_pointer_cast<table_array>(
1873  curr_table->get(part));
1874  arr->get().push_back(make_table());
1875  curr_table = arr->get().back().get();
1876  }
1877  // otherwise, create the implicitly defined table and move
1878  // down to it
1879  else
1880  {
1881  curr_table->insert(part, make_table());
1882  curr_table
1883  = static_cast<table*>(curr_table->get(part).get());
1884  }
1885  }
1886  }
1887 
1888  // consume the last "]]"
1889  if (it == end)
1890  throw_parse_exception("Unterminated table array name");
1891  ++it;
1892  if (it == end)
1893  throw_parse_exception("Unterminated table array name");
1894  ++it;
1895 
1896  consume_whitespace(it, end);
1897  eol_or_comment(it, end);
1898  }
1899 
1900  void parse_key_value(std::string::iterator& it, std::string::iterator& end,
1901  table* curr_table)
1902  {
1903  auto key = parse_key(it, end, [](char c) { return c == '='; });
1904  if (curr_table->contains(key))
1905  throw_parse_exception("Key " + key + " already present");
1906  if (it == end || *it != '=')
1907  throw_parse_exception("Value must follow after a '='");
1908  ++it;
1909  consume_whitespace(it, end);
1910  curr_table->insert(key, parse_value(it, end));
1911  consume_whitespace(it, end);
1912  }
1913 
1914  template <class Function>
1915  std::string parse_key(std::string::iterator& it,
1916  const std::string::iterator& end, Function&& fun)
1917  {
1918  consume_whitespace(it, end);
1919  if (*it == '"')
1920  {
1921  return parse_quoted_key(it, end);
1922  }
1923  else
1924  {
1925  auto bke = std::find_if(it, end, std::forward<Function>(fun));
1926  return parse_bare_key(it, bke);
1927  }
1928  }
1929 
1930  std::string parse_bare_key(std::string::iterator& it,
1931  const std::string::iterator& end)
1932  {
1933  if (it == end)
1934  {
1935  throw_parse_exception("Bare key missing name");
1936  }
1937 
1938  auto key_end = end;
1939  --key_end;
1940  consume_backwards_whitespace(key_end, it);
1941  ++key_end;
1942  std::string key{it, key_end};
1943 
1944  if (std::find(it, key_end, '#') != key_end)
1945  {
1946  throw_parse_exception("Bare key " + key + " cannot contain #");
1947  }
1948 
1949  if (std::find_if(it, key_end,
1950  [](char c) { return c == ' ' || c == '\t'; })
1951  != key_end)
1952  {
1953  throw_parse_exception("Bare key " + key
1954  + " cannot contain whitespace");
1955  }
1956 
1957  if (std::find_if(it, key_end,
1958  [](char c) { return c == '[' || c == ']'; })
1959  != key_end)
1960  {
1961  throw_parse_exception("Bare key " + key
1962  + " cannot contain '[' or ']'");
1963  }
1964 
1965  it = end;
1966  return key;
1967  }
1968 
1969  std::string parse_quoted_key(std::string::iterator& it,
1970  const std::string::iterator& end)
1971  {
1972  return string_literal(it, end, '"');
1973  }
1974 
1975  enum class parse_type
1976  {
1977  STRING = 1,
1978  LOCAL_TIME,
1979  LOCAL_DATE,
1980  LOCAL_DATETIME,
1981  OFFSET_DATETIME,
1982  INT,
1983  FLOAT,
1984  BOOL,
1985  ARRAY,
1986  INLINE_TABLE
1987  };
1988 
1989  std::shared_ptr<base> parse_value(std::string::iterator& it,
1990  std::string::iterator& end)
1991  {
1992  parse_type type = determine_value_type(it, end);
1993  switch (type)
1994  {
1995  case parse_type::STRING:
1996  return parse_string(it, end);
1997  case parse_type::LOCAL_TIME:
1998  return parse_time(it, end);
1999  case parse_type::LOCAL_DATE:
2000  case parse_type::LOCAL_DATETIME:
2001  case parse_type::OFFSET_DATETIME:
2002  return parse_date(it, end);
2003  case parse_type::INT:
2004  case parse_type::FLOAT:
2005  return parse_number(it, end);
2006  case parse_type::BOOL:
2007  return parse_bool(it, end);
2008  case parse_type::ARRAY:
2009  return parse_array(it, end);
2010  case parse_type::INLINE_TABLE:
2011  return parse_inline_table(it, end);
2012  default:
2013  throw_parse_exception("Failed to parse value");
2014  }
2015  }
2016 
2017  parse_type determine_value_type(const std::string::iterator& it,
2018  const std::string::iterator& end)
2019  {
2020  if (*it == '"' || *it == '\'')
2021  {
2022  return parse_type::STRING;
2023  }
2024  else if (is_time(it, end))
2025  {
2026  return parse_type::LOCAL_TIME;
2027  }
2028  else if (auto dtype = date_type(it, end))
2029  {
2030  return *dtype;
2031  }
2032  else if (is_number(*it) || *it == '-' || *it == '+')
2033  {
2034  return determine_number_type(it, end);
2035  }
2036  else if (*it == 't' || *it == 'f')
2037  {
2038  return parse_type::BOOL;
2039  }
2040  else if (*it == '[')
2041  {
2042  return parse_type::ARRAY;
2043  }
2044  else if (*it == '{')
2045  {
2046  return parse_type::INLINE_TABLE;
2047  }
2048  throw_parse_exception("Failed to parse value type");
2049  }
2050 
2051  parse_type determine_number_type(const std::string::iterator& it,
2052  const std::string::iterator& end)
2053  {
2054  // determine if we are an integer or a float
2055  auto check_it = it;
2056  if (*check_it == '-' || *check_it == '+')
2057  ++check_it;
2058  while (check_it != end && is_number(*check_it))
2059  ++check_it;
2060  if (check_it != end && *check_it == '.')
2061  {
2062  ++check_it;
2063  while (check_it != end && is_number(*check_it))
2064  ++check_it;
2065  return parse_type::FLOAT;
2066  }
2067  else
2068  {
2069  return parse_type::INT;
2070  }
2071  }
2072 
2073  std::shared_ptr<value<std::string>> parse_string(std::string::iterator& it,
2074  std::string::iterator& end)
2075  {
2076  auto delim = *it;
2077  assert(delim == '"' || delim == '\'');
2078 
2079  // end is non-const here because we have to be able to potentially
2080  // parse multiple lines in a string, not just one
2081  auto check_it = it;
2082  ++check_it;
2083  if (check_it != end && *check_it == delim)
2084  {
2085  ++check_it;
2086  if (check_it != end && *check_it == delim)
2087  {
2088  it = ++check_it;
2089  return parse_multiline_string(it, end, delim);
2090  }
2091  }
2092  return make_value<std::string>(string_literal(it, end, delim));
2093  }
2094 
2095  std::shared_ptr<value<std::string>>
2096  parse_multiline_string(std::string::iterator& it,
2097  std::string::iterator& end, char delim)
2098  {
2099  std::stringstream ss;
2100 
2101  auto is_ws = [](char c) { return c == ' ' || c == '\t'; };
2102 
2103  bool consuming = false;
2104  std::shared_ptr<value<std::string>> ret;
2105 
2106  auto handle_line
2107  = [&](std::string::iterator& it, std::string::iterator& end) {
2108  if (consuming)
2109  {
2110  it = std::find_if_not(it, end, is_ws);
2111 
2112  // whole line is whitespace
2113  if (it == end)
2114  return;
2115  }
2116 
2117  consuming = false;
2118 
2119  while (it != end)
2120  {
2121  // handle escaped characters
2122  if (delim == '"' && *it == '\\')
2123  {
2124  auto check = it;
2125  // check if this is an actual escape sequence or a
2126  // whitespace escaping backslash
2127  ++check;
2128  consume_whitespace(check, end);
2129  if (check == end)
2130  {
2131  consuming = true;
2132  break;
2133  }
2134 
2135  ss << parse_escape_code(it, end);
2136  continue;
2137  }
2138 
2139  // if we can end the string
2140  if (std::distance(it, end) >= 3)
2141  {
2142  auto check = it;
2143  // check for """
2144  if (*check++ == delim && *check++ == delim
2145  && *check++ == delim)
2146  {
2147  it = check;
2148  ret = make_value<std::string>(ss.str());
2149  break;
2150  }
2151  }
2152 
2153  ss << *it++;
2154  }
2155  };
2156 
2157  // handle the remainder of the current line
2158  handle_line(it, end);
2159  if (ret)
2160  return ret;
2161 
2162  // start eating lines
2163  while (detail::getline(input_, line_))
2164  {
2165  ++line_number_;
2166 
2167  it = line_.begin();
2168  end = line_.end();
2169 
2170  handle_line(it, end);
2171 
2172  if (ret)
2173  return ret;
2174 
2175  if (!consuming)
2176  ss << std::endl;
2177  }
2178 
2179  throw_parse_exception("Unterminated multi-line basic string");
2180  }
2181 
2182  std::string string_literal(std::string::iterator& it,
2183  const std::string::iterator& end, char delim)
2184  {
2185  ++it;
2186  std::string val;
2187  while (it != end)
2188  {
2189  // handle escaped characters
2190  if (delim == '"' && *it == '\\')
2191  {
2192  val += parse_escape_code(it, end);
2193  }
2194  else if (*it == delim)
2195  {
2196  ++it;
2197  consume_whitespace(it, end);
2198  return val;
2199  }
2200  else
2201  {
2202  val += *it++;
2203  }
2204  }
2205  throw_parse_exception("Unterminated string literal");
2206  }
2207 
2208  std::string parse_escape_code(std::string::iterator& it,
2209  const std::string::iterator& end)
2210  {
2211  ++it;
2212  if (it == end)
2213  throw_parse_exception("Invalid escape sequence");
2214  char value;
2215  if (*it == 'b')
2216  {
2217  value = '\b';
2218  }
2219  else if (*it == 't')
2220  {
2221  value = '\t';
2222  }
2223  else if (*it == 'n')
2224  {
2225  value = '\n';
2226  }
2227  else if (*it == 'f')
2228  {
2229  value = '\f';
2230  }
2231  else if (*it == 'r')
2232  {
2233  value = '\r';
2234  }
2235  else if (*it == '"')
2236  {
2237  value = '"';
2238  }
2239  else if (*it == '\\')
2240  {
2241  value = '\\';
2242  }
2243  else if (*it == 'u' || *it == 'U')
2244  {
2245  return parse_unicode(it, end);
2246  }
2247  else
2248  {
2249  throw_parse_exception("Invalid escape sequence");
2250  }
2251  ++it;
2252  return std::string(1, value);
2253  }
2254 
2255  std::string parse_unicode(std::string::iterator& it,
2256  const std::string::iterator& end)
2257  {
2258  bool large = *it++ == 'U';
2259  auto codepoint = parse_hex(it, end, large ? 0x10000000 : 0x1000);
2260 
2261  if ((codepoint > 0xd7ff && codepoint < 0xe000) || codepoint > 0x10ffff)
2262  {
2263  throw_parse_exception(
2264  "Unicode escape sequence is not a Unicode scalar value");
2265  }
2266 
2267  std::string result;
2268  // See Table 3-6 of the Unicode standard
2269  if (codepoint <= 0x7f)
2270  {
2271  // 1-byte codepoints: 00000000 0xxxxxxx
2272  // repr: 0xxxxxxx
2273  result += static_cast<char>(codepoint & 0x7f);
2274  }
2275  else if (codepoint <= 0x7ff)
2276  {
2277  // 2-byte codepoints: 00000yyy yyxxxxxx
2278  // repr: 110yyyyy 10xxxxxx
2279  //
2280  // 0x1f = 00011111
2281  // 0xc0 = 11000000
2282  //
2283  result += static_cast<char>(0xc0 | ((codepoint >> 6) & 0x1f));
2284  //
2285  // 0x80 = 10000000
2286  // 0x3f = 00111111
2287  //
2288  result += static_cast<char>(0x80 | (codepoint & 0x3f));
2289  }
2290  else if (codepoint <= 0xffff)
2291  {
2292  // 3-byte codepoints: zzzzyyyy yyxxxxxx
2293  // repr: 1110zzzz 10yyyyyy 10xxxxxx
2294  //
2295  // 0xe0 = 11100000
2296  // 0x0f = 00001111
2297  //
2298  result += static_cast<char>(0xe0 | ((codepoint >> 12) & 0x0f));
2299  result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x1f));
2300  result += static_cast<char>(0x80 | (codepoint & 0x3f));
2301  }
2302  else
2303  {
2304  // 4-byte codepoints: 000uuuuu zzzzyyyy yyxxxxxx
2305  // repr: 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
2306  //
2307  // 0xf0 = 11110000
2308  // 0x07 = 00000111
2309  //
2310  result += static_cast<char>(0xf0 | ((codepoint >> 18) & 0x07));
2311  result += static_cast<char>(0x80 | ((codepoint >> 12) & 0x3f));
2312  result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f));
2313  result += static_cast<char>(0x80 | (codepoint & 0x3f));
2314  }
2315  return result;
2316  }
2317 
2318  uint32_t parse_hex(std::string::iterator& it,
2319  const std::string::iterator& end, uint32_t place)
2320  {
2321  uint32_t value = 0;
2322  while (place > 0)
2323  {
2324  if (it == end)
2325  throw_parse_exception("Unexpected end of unicode sequence");
2326 
2327  if (!is_hex(*it))
2328  throw_parse_exception("Invalid unicode escape sequence");
2329 
2330  value += place * hex_to_digit(*it++);
2331  place /= 16;
2332  }
2333  return value;
2334  }
2335 
2336  bool is_hex(char c)
2337  {
2338  return is_number(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
2339  }
2340 
2341  uint32_t hex_to_digit(char c)
2342  {
2343  if (is_number(c))
2344  return static_cast<uint32_t>(c - '0');
2345  return 10 + static_cast<uint32_t>(
2346  c - ((c >= 'a' && c <= 'f') ? 'a' : 'A'));
2347  }
2348 
2349  std::shared_ptr<base> parse_number(std::string::iterator& it,
2350  const std::string::iterator& end)
2351  {
2352  auto check_it = it;
2353  auto check_end = find_end_of_number(it, end);
2354 
2355  auto eat_sign = [&]() {
2356  if (check_it != end && (*check_it == '-' || *check_it == '+'))
2357  ++check_it;
2358  };
2359 
2360  eat_sign();
2361 
2362  auto eat_numbers = [&]() {
2363  auto beg = check_it;
2364  while (check_it != end && is_number(*check_it))
2365  {
2366  ++check_it;
2367  if (check_it != end && *check_it == '_')
2368  {
2369  ++check_it;
2370  if (check_it == end || !is_number(*check_it))
2371  throw_parse_exception("Malformed number");
2372  }
2373  }
2374 
2375  if (check_it == beg)
2376  throw_parse_exception("Malformed number");
2377  };
2378 
2379  auto check_no_leading_zero = [&]() {
2380  if (check_it != end && *check_it == '0' && check_it + 1 != check_end
2381  && check_it[1] != '.')
2382  {
2383  throw_parse_exception("Numbers may not have leading zeros");
2384  }
2385  };
2386 
2387  check_no_leading_zero();
2388  eat_numbers();
2389 
2390  if (check_it != end
2391  && (*check_it == '.' || *check_it == 'e' || *check_it == 'E'))
2392  {
2393  bool is_exp = *check_it == 'e' || *check_it == 'E';
2394 
2395  ++check_it;
2396  if (check_it == end)
2397  throw_parse_exception("Floats must have trailing digits");
2398 
2399  auto eat_exp = [&]() {
2400  eat_sign();
2401  check_no_leading_zero();
2402  eat_numbers();
2403  };
2404 
2405  if (is_exp)
2406  eat_exp();
2407  else
2408  eat_numbers();
2409 
2410  if (!is_exp && check_it != end
2411  && (*check_it == 'e' || *check_it == 'E'))
2412  {
2413  ++check_it;
2414  eat_exp();
2415  }
2416 
2417  return parse_float(it, check_it);
2418  }
2419  else
2420  {
2421  return parse_int(it, check_it);
2422  }
2423  }
2424 
2425  std::shared_ptr<value<int64_t>> parse_int(std::string::iterator& it,
2426  const std::string::iterator& end)
2427  {
2428  std::string v{it, end};
2429  v.erase(std::remove(v.begin(), v.end(), '_'), v.end());
2430  it = end;
2431  try
2432  {
2433  return make_value<int64_t>(std::stoll(v));
2434  }
2435  catch (const std::invalid_argument& ex)
2436  {
2437  throw_parse_exception("Malformed number (invalid argument: "
2438  + std::string{ex.what()} + ")");
2439  }
2440  catch (const std::out_of_range& ex)
2441  {
2442  throw_parse_exception("Malformed number (out of range: "
2443  + std::string{ex.what()} + ")");
2444  }
2445  }
2446 
2447  std::shared_ptr<value<double>> parse_float(std::string::iterator& it,
2448  const std::string::iterator& end)
2449  {
2450  std::string v{it, end};
2451  v.erase(std::remove(v.begin(), v.end(), '_'), v.end());
2452  it = end;
2453  try
2454  {
2455  return make_value<double>(std::stod(v));
2456  }
2457  catch (const std::invalid_argument& ex)
2458  {
2459  throw_parse_exception("Malformed number (invalid argument: "
2460  + std::string{ex.what()} + ")");
2461  }
2462  catch (const std::out_of_range& ex)
2463  {
2464  throw_parse_exception("Malformed number (out of range: "
2465  + std::string{ex.what()} + ")");
2466  }
2467  }
2468 
2469  std::shared_ptr<value<bool>> parse_bool(std::string::iterator& it,
2470  const std::string::iterator& end)
2471  {
2472  auto eat = make_consumer(it, end, [this]() {
2473  throw_parse_exception("Attempted to parse invalid boolean value");
2474  });
2475 
2476  if (*it == 't')
2477  {
2478  eat("true");
2479  return make_value<bool>(true);
2480  }
2481  else if (*it == 'f')
2482  {
2483  eat("false");
2484  return make_value<bool>(false);
2485  }
2486 
2487  eat.error();
2488  return nullptr;
2489  }
2490 
2491  std::string::iterator find_end_of_number(std::string::iterator it,
2492  std::string::iterator end)
2493  {
2494  return std::find_if(it, end, [](char c) {
2495  return !is_number(c) && c != '_' && c != '.' && c != 'e' && c != 'E'
2496  && c != '-' && c != '+';
2497  });
2498  }
2499 
2500  std::string::iterator find_end_of_date(std::string::iterator it,
2501  std::string::iterator end)
2502  {
2503  return std::find_if(it, end, [](char c) {
2504  return !is_number(c) && c != 'T' && c != 'Z' && c != ':' && c != '-'
2505  && c != '+' && c != '.';
2506  });
2507  }
2508 
2509  std::string::iterator find_end_of_time(std::string::iterator it,
2510  std::string::iterator end)
2511  {
2512  return std::find_if(it, end, [](char c) {
2513  return !is_number(c) && c != ':' && c != '.';
2514  });
2515  }
2516 
2517  local_time read_time(std::string::iterator& it,
2518  const std::string::iterator& end)
2519  {
2520  auto time_end = find_end_of_time(it, end);
2521 
2522  auto eat = make_consumer(
2523  it, time_end, [&]() { throw_parse_exception("Malformed time"); });
2524 
2525  local_time ltime;
2526 
2527  ltime.hour = eat.eat_digits(2);
2528  eat(':');
2529  ltime.minute = eat.eat_digits(2);
2530  eat(':');
2531  ltime.second = eat.eat_digits(2);
2532 
2533  int power = 100000;
2534  if (it != time_end && *it == '.')
2535  {
2536  ++it;
2537  while (it != time_end && is_number(*it))
2538  {
2539  ltime.microsecond += power * (*it++ - '0');
2540  power /= 10;
2541  }
2542  }
2543 
2544  if (it != time_end)
2545  throw_parse_exception("Malformed time");
2546 
2547  return ltime;
2548  }
2549 
2550  std::shared_ptr<value<local_time>>
2551  parse_time(std::string::iterator& it, const std::string::iterator& end)
2552  {
2553  return make_value(read_time(it, end));
2554  }
2555 
2556  std::shared_ptr<base> parse_date(std::string::iterator& it,
2557  const std::string::iterator& end)
2558  {
2559  auto date_end = find_end_of_date(it, end);
2560 
2561  auto eat = make_consumer(
2562  it, date_end, [&]() { throw_parse_exception("Malformed date"); });
2563 
2564  local_date ldate;
2565  ldate.year = eat.eat_digits(4);
2566  eat('-');
2567  ldate.month = eat.eat_digits(2);
2568  eat('-');
2569  ldate.day = eat.eat_digits(2);
2570 
2571  if (it == date_end)
2572  return make_value(ldate);
2573 
2574  eat('T');
2575 
2576  local_datetime ldt;
2577  static_cast<local_date&>(ldt) = ldate;
2578  static_cast<local_time&>(ldt) = read_time(it, date_end);
2579 
2580  if (it == date_end)
2581  return make_value(ldt);
2582 
2583  offset_datetime dt;
2584  static_cast<local_datetime&>(dt) = ldt;
2585 
2586  int hoff = 0;
2587  int moff = 0;
2588  if (*it == '+' || *it == '-')
2589  {
2590  auto plus = *it == '+';
2591  ++it;
2592 
2593  hoff = eat.eat_digits(2);
2594  dt.hour_offset = (plus) ? hoff : -hoff;
2595  eat(':');
2596  moff = eat.eat_digits(2);
2597  dt.minute_offset = (plus) ? moff : -moff;
2598  }
2599  else if (*it == 'Z')
2600  {
2601  ++it;
2602  }
2603 
2604  if (it != date_end)
2605  throw_parse_exception("Malformed date");
2606 
2607  return make_value(dt);
2608  }
2609 
2610  std::shared_ptr<base> parse_array(std::string::iterator& it,
2611  std::string::iterator& end)
2612  {
2613  // this gets ugly because of the "homogeneity" restriction:
2614  // arrays can either be of only one type, or contain arrays
2615  // (each of those arrays could be of different types, though)
2616  //
2617  // because of the latter portion, we don't really have a choice
2618  // but to represent them as arrays of base values...
2619  ++it;
2620 
2621  // ugh---have to read the first value to determine array type...
2622  skip_whitespace_and_comments(it, end);
2623 
2624  // edge case---empty array
2625  if (*it == ']')
2626  {
2627  ++it;
2628  return make_array();
2629  }
2630 
2631  auto val_end = std::find_if(
2632  it, end, [](char c) { return c == ',' || c == ']' || c == '#'; });
2633  parse_type type = determine_value_type(it, val_end);
2634  switch (type)
2635  {
2636  case parse_type::STRING:
2637  return parse_value_array<std::string>(it, end);
2638  case parse_type::LOCAL_TIME:
2639  return parse_value_array<local_time>(it, end);
2640  case parse_type::LOCAL_DATE:
2641  return parse_value_array<local_date>(it, end);
2642  case parse_type::LOCAL_DATETIME:
2643  return parse_value_array<local_datetime>(it, end);
2644  case parse_type::OFFSET_DATETIME:
2645  return parse_value_array<offset_datetime>(it, end);
2646  case parse_type::INT:
2647  return parse_value_array<int64_t>(it, end);
2648  case parse_type::FLOAT:
2649  return parse_value_array<double>(it, end);
2650  case parse_type::BOOL:
2651  return parse_value_array<bool>(it, end);
2652  case parse_type::ARRAY:
2653  return parse_object_array<array>(&parser::parse_array, '[', it,
2654  end);
2655  case parse_type::INLINE_TABLE:
2656  return parse_object_array<table_array>(
2657  &parser::parse_inline_table, '{', it, end);
2658  default:
2659  throw_parse_exception("Unable to parse array");
2660  }
2661  }
2662 
2663  template <class Value>
2664  std::shared_ptr<array> parse_value_array(std::string::iterator& it,
2665  std::string::iterator& end)
2666  {
2667  auto arr = make_array();
2668  while (it != end && *it != ']')
2669  {
2670  auto value = parse_value(it, end);
2671  if (auto v = value->as<Value>())
2672  arr->get().push_back(value);
2673  else
2674  throw_parse_exception("Arrays must be heterogeneous");
2675  skip_whitespace_and_comments(it, end);
2676  if (*it != ',')
2677  break;
2678  ++it;
2679  skip_whitespace_and_comments(it, end);
2680  }
2681  if (it != end)
2682  ++it;
2683  return arr;
2684  }
2685 
2686  template <class Object, class Function>
2687  std::shared_ptr<Object> parse_object_array(Function&& fun, char delim,
2688  std::string::iterator& it,
2689  std::string::iterator& end)
2690  {
2691  auto arr = make_element<Object>();
2692 
2693  while (it != end && *it != ']')
2694  {
2695  if (*it != delim)
2696  throw_parse_exception("Unexpected character in array");
2697 
2698  arr->get().push_back(((*this).*fun)(it, end));
2699  skip_whitespace_and_comments(it, end);
2700 
2701  if (*it != ',')
2702  break;
2703 
2704  ++it;
2705  skip_whitespace_and_comments(it, end);
2706  }
2707 
2708  if (it == end || *it != ']')
2709  throw_parse_exception("Unterminated array");
2710 
2711  ++it;
2712  return arr;
2713  }
2714 
2715  std::shared_ptr<table> parse_inline_table(std::string::iterator& it,
2716  std::string::iterator& end)
2717  {
2718  auto tbl = make_table();
2719  do
2720  {
2721  ++it;
2722  if (it == end)
2723  throw_parse_exception("Unterminated inline table");
2724 
2725  consume_whitespace(it, end);
2726  parse_key_value(it, end, tbl.get());
2727  consume_whitespace(it, end);
2728  } while (*it == ',');
2729 
2730  if (it == end || *it != '}')
2731  throw_parse_exception("Unterminated inline table");
2732 
2733  ++it;
2734  consume_whitespace(it, end);
2735 
2736  return tbl;
2737  }
2738 
2739  void skip_whitespace_and_comments(std::string::iterator& start,
2740  std::string::iterator& end)
2741  {
2742  consume_whitespace(start, end);
2743  while (start == end || *start == '#')
2744  {
2745  if (!detail::getline(input_, line_))
2746  throw_parse_exception("Unclosed array");
2747  line_number_++;
2748  start = line_.begin();
2749  end = line_.end();
2750  consume_whitespace(start, end);
2751  }
2752  }
2753 
2754  void consume_whitespace(std::string::iterator& it,
2755  const std::string::iterator& end)
2756  {
2757  while (it != end && (*it == ' ' || *it == '\t'))
2758  ++it;
2759  }
2760 
2761  void consume_backwards_whitespace(std::string::iterator& back,
2762  const std::string::iterator& front)
2763  {
2764  while (back != front && (*back == ' ' || *back == '\t'))
2765  --back;
2766  }
2767 
2768  void eol_or_comment(const std::string::iterator& it,
2769  const std::string::iterator& end)
2770  {
2771  if (it != end && *it != '#')
2772  throw_parse_exception("Unidentified trailing character '"
2773  + std::string{*it}
2774  + "'---did you forget a '#'?");
2775  }
2776 
2777  bool is_time(const std::string::iterator& it,
2778  const std::string::iterator& end)
2779  {
2780  auto time_end = find_end_of_time(it, end);
2781  auto len = std::distance(it, time_end);
2782 
2783  if (len < 8)
2784  return false;
2785 
2786  if (it[2] != ':' || it[5] != ':')
2787  return false;
2788 
2789  if (len > 8)
2790  return it[8] == '.' && len > 9;
2791 
2792  return true;
2793  }
2794 
2795  option<parse_type> date_type(const std::string::iterator& it,
2796  const std::string::iterator& end)
2797  {
2798  auto date_end = find_end_of_date(it, end);
2799  auto len = std::distance(it, date_end);
2800 
2801  if (len < 10)
2802  return {};
2803 
2804  if (it[4] != '-' || it[7] != '-')
2805  return {};
2806 
2807  if (len >= 19 && it[10] == 'T' && is_time(it + 11, date_end))
2808  {
2809  // datetime type
2810  auto time_end = find_end_of_time(it + 11, date_end);
2811  if (time_end == date_end)
2812  return {parse_type::LOCAL_DATETIME};
2813  else
2814  return {parse_type::OFFSET_DATETIME};
2815  }
2816  else if (len == 10)
2817  {
2818  // just a regular date
2819  return {parse_type::LOCAL_DATE};
2820  }
2821 
2822  return {};
2823  }
2824 
2825  std::istream& input_;
2826  std::string line_;
2827  std::size_t line_number_ = 0;
2828 };
2829 
2834 inline std::shared_ptr<table> parse_file(const std::string& filename)
2835 {
2836 #if defined(BOOST_NOWIDE_FSTREAM_INCLUDED_HPP)
2837  boost::nowide::ifstream file{filename.c_str()};
2838 #elif defined(NOWIDE_FSTREAM_INCLUDED_HPP)
2839  nowide::ifstream file{filename.c_str()};
2840 #else
2841  std::ifstream file{filename};
2842 #endif
2843  if (!file.is_open())
2844  throw parse_exception{filename + " could not be opened for parsing"};
2845  parser p{file};
2846  return p.parse();
2847 }
2848 
2849 template <class... Ts>
2851 
2852 template <>
2854 {
2855  template <class Visitor, class... Args>
2856  static void accept(const base&, Visitor&&, Args&&...)
2857  {
2858  // nothing
2859  }
2860 };
2861 
2862 template <class T, class... Ts>
2863 struct value_accept<T, Ts...>
2864 {
2865  template <class Visitor, class... Args>
2866  static void accept(const base& b, Visitor&& visitor, Args&&... args)
2867  {
2868  if (auto v = b.as<T>())
2869  {
2870  visitor.visit(*v, std::forward<Args>(args)...);
2871  }
2872  else
2873  {
2874  value_accept<Ts...>::accept(b, std::forward<Visitor>(visitor),
2875  std::forward<Args>(args)...);
2876  }
2877  }
2878 };
2879 
2884 template <class Visitor, class... Args>
2885 void base::accept(Visitor&& visitor, Args&&... args) const
2886 {
2887  if (is_value())
2888  {
2889  using value_acceptor
2890  = value_accept<std::string, int64_t, double, bool, local_date,
2892  value_acceptor::accept(*this, std::forward<Visitor>(visitor),
2893  std::forward<Args>(args)...);
2894  }
2895  else if (is_table())
2896  {
2897  visitor.visit(static_cast<const table&>(*this),
2898  std::forward<Args>(args)...);
2899  }
2900  else if (is_array())
2901  {
2902  visitor.visit(static_cast<const array&>(*this),
2903  std::forward<Args>(args)...);
2904  }
2905  else if (is_table_array())
2906  {
2907  visitor.visit(static_cast<const table_array&>(*this),
2908  std::forward<Args>(args)...);
2909  }
2910 }
2911 
2917 {
2918  public:
2922  toml_writer(std::ostream& s, const std::string& indent_space = "\t")
2923  : stream_(s), indent_(indent_space), has_naked_endline_(false)
2924  {
2925  // nothing
2926  }
2927 
2928  public:
2932  template <class T>
2933  void visit(const value<T>& v, bool = false)
2934  {
2935  write(v);
2936  }
2937 
2941  void visit(const table& t, bool in_array = false)
2942  {
2943  write_table_header(in_array);
2944  std::vector<std::string> values;
2945  std::vector<std::string> tables;
2946 
2947  for (const auto& i : t)
2948  {
2949  if (i.second->is_table() || i.second->is_table_array())
2950  {
2951  tables.push_back(i.first);
2952  }
2953  else
2954  {
2955  values.push_back(i.first);
2956  }
2957  }
2958 
2959  for (unsigned int i = 0; i < values.size(); ++i)
2960  {
2961  path_.push_back(values[i]);
2962 
2963  if (i > 0)
2964  endline();
2965 
2966  write_table_item_header(*t.get(values[i]));
2967  t.get(values[i])->accept(*this, false);
2968  path_.pop_back();
2969  }
2970 
2971  for (unsigned int i = 0; i < tables.size(); ++i)
2972  {
2973  path_.push_back(tables[i]);
2974 
2975  if (values.size() > 0 || i > 0)
2976  endline();
2977 
2978  write_table_item_header(*t.get(tables[i]));
2979  t.get(tables[i])->accept(*this, false);
2980  path_.pop_back();
2981  }
2982 
2983  endline();
2984  }
2985 
2989  void visit(const array& a, bool = false)
2990  {
2991  write("[");
2992 
2993  for (unsigned int i = 0; i < a.get().size(); ++i)
2994  {
2995  if (i > 0)
2996  write(", ");
2997 
2998  if (a.get()[i]->is_array())
2999  {
3000  a.get()[i]->as_array()->accept(*this, true);
3001  }
3002  else
3003  {
3004  a.get()[i]->accept(*this, true);
3005  }
3006  }
3007 
3008  write("]");
3009  }
3010 
3014  void visit(const table_array& t, bool = false)
3015  {
3016  for (unsigned int j = 0; j < t.get().size(); ++j)
3017  {
3018  if (j > 0)
3019  endline();
3020 
3021  t.get()[j]->accept(*this, true);
3022  }
3023 
3024  endline();
3025  }
3026 
3030  static std::string escape_string(const std::string& str)
3031  {
3032  std::string res;
3033  for (auto it = str.begin(); it != str.end(); ++it)
3034  {
3035  if (*it == '\b')
3036  {
3037  res += "\\b";
3038  }
3039  else if (*it == '\t')
3040  {
3041  res += "\\t";
3042  }
3043  else if (*it == '\n')
3044  {
3045  res += "\\n";
3046  }
3047  else if (*it == '\f')
3048  {
3049  res += "\\f";
3050  }
3051  else if (*it == '\r')
3052  {
3053  res += "\\r";
3054  }
3055  else if (*it == '"')
3056  {
3057  res += "\\\"";
3058  }
3059  else if (*it == '\\')
3060  {
3061  res += "\\\\";
3062  }
3063  else if (*it >= 0x0000 && *it <= 0x001f)
3064  {
3065  res += "\\u";
3066  std::stringstream ss;
3067  ss << std::hex << static_cast<uint32_t>(*it);
3068  res += ss.str();
3069  }
3070  else
3071  {
3072  res += *it;
3073  }
3074  }
3075  return res;
3076  }
3077 
3078  protected:
3082  void write(const value<std::string>& v)
3083  {
3084  write("\"");
3085  write(escape_string(v.get()));
3086  write("\"");
3087  }
3088 
3092  void write(const value<double>& v)
3093  {
3094  std::ios::fmtflags flags{stream_.flags()};
3095 
3096  stream_ << std::showpoint;
3097  write(v.get());
3098 
3099  stream_.flags(flags);
3100  }
3101 
3106  template <class T>
3107  typename std::enable_if<is_one_of<T, int64_t, local_date, local_time,
3109  offset_datetime>::value>::type
3110  write(const value<T>& v)
3111  {
3112  write(v.get());
3113  }
3114 
3118  void write(const value<bool>& v)
3119  {
3120  write((v.get() ? "true" : "false"));
3121  }
3122 
3126  void write_table_header(bool in_array = false)
3127  {
3128  if (!path_.empty())
3129  {
3130  indent();
3131 
3132  write("[");
3133 
3134  if (in_array)
3135  {
3136  write("[");
3137  }
3138 
3139  for (unsigned int i = 0; i < path_.size(); ++i)
3140  {
3141  if (i > 0)
3142  {
3143  write(".");
3144  }
3145 
3146  if (path_[i].find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcde"
3147  "fghijklmnopqrstuvwxyz0123456789"
3148  "_-")
3149  == std::string::npos)
3150  {
3151  write(path_[i]);
3152  }
3153  else
3154  {
3155  write("\"");
3156  write(escape_string(path_[i]));
3157  write("\"");
3158  }
3159  }
3160 
3161  if (in_array)
3162  {
3163  write("]");
3164  }
3165 
3166  write("]");
3167  endline();
3168  }
3169  }
3170 
3175  {
3176  if (!b.is_table() && !b.is_table_array())
3177  {
3178  indent();
3179 
3180  if (path_.back().find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcde"
3181  "fghijklmnopqrstuvwxyz0123456789"
3182  "_-")
3183  == std::string::npos)
3184  {
3185  write(path_.back());
3186  }
3187  else
3188  {
3189  write("\"");
3190  write(escape_string(path_.back()));
3191  write("\"");
3192  }
3193 
3194  write(" = ");
3195  }
3196  }
3197 
3198  private:
3203  void indent()
3204  {
3205  for (std::size_t i = 1; i < path_.size(); ++i)
3206  write(indent_);
3207  }
3208 
3212  template <class T>
3213  void write(const T& v)
3214  {
3215  stream_ << v;
3216  has_naked_endline_ = false;
3217  }
3218 
3222  void endline()
3223  {
3224  if (!has_naked_endline_)
3225  {
3226  stream_ << "\n";
3227  has_naked_endline_ = true;
3228  }
3229  }
3230 
3231  private:
3232  std::ostream& stream_;
3233  const std::string indent_;
3234  std::vector<std::string> path_;
3235  bool has_naked_endline_;
3236 };
3237 
3238 inline std::ostream& operator<<(std::ostream& stream, const base& b)
3239 {
3240  toml_writer writer{stream};
3241  b.accept(writer);
3242  return stream;
3243 }
3244 
3245 template <class T>
3246 std::ostream& operator<<(std::ostream& stream, const value<T>& v)
3247 {
3248  toml_writer writer{stream};
3249  v.accept(writer);
3250  return stream;
3251 }
3252 
3253 inline std::ostream& operator<<(std::ostream& stream, const table& t)
3254 {
3255  toml_writer writer{stream};
3256  t.accept(writer);
3257  return stream;
3258 }
3259 
3260 inline std::ostream& operator<<(std::ostream& stream, const table_array& t)
3261 {
3262  toml_writer writer{stream};
3263  t.accept(writer);
3264  return stream;
3265 }
3266 
3267 inline std::ostream& operator<<(std::ostream& stream, const array& a)
3268 {
3269  toml_writer writer{stream};
3270  a.accept(writer);
3271  return stream;
3272 }
3273 }
3274 #endif
std::shared_ptr< base > get_qualified(const std::string &key) const
Obtains the base for a given key.
Definition: cpptoml.h:1175
Definition: cpptoml.h:615
iterator insert(iterator position, const std::shared_ptr< value< T >> &value)
Insert a value into the array.
Definition: cpptoml.h:779
void insert(const std::string &key, const std::shared_ptr< base > &value)
Adds an element to the keytable.
Definition: cpptoml.h:1346
std::shared_ptr< value< T > > as()
Attempts to coerce the TOML element into a concrete TOML value of type T.
Definition: cpptoml.h:560
iterator insert(iterator position, const std::shared_ptr< array > &value)
Insert an array into the array.
Definition: cpptoml.h:794
void reserve(size_type n)
Reserve space for n tables.
Definition: cpptoml.h:988
void push_back(const std::shared_ptr< value< T >> &val)
Add a value to the end of the array.
Definition: cpptoml.h:738
array_of_trait< T >::return_type get_qualified_array_of(const std::string &key) const
Helper function that attempts to get an array of values of a given type corresponding to the template...
Definition: cpptoml.h:1323
std::shared_ptr< table_array > as_table_array()
Converts the TOML element into a table array.
Definition: cpptoml.h:462
std::shared_ptr< array > get_array_qualified(const std::string &key) const
Obtains an array for a given key.
Definition: cpptoml.h:1216
Definition: cpptoml.h:282
virtual bool is_table_array() const override
Determines if the given TOML element is an array of tables.
Definition: cpptoml.h:938
void visit(const value< T > &v, bool=false)
Output a base value of the TOML tree.
Definition: cpptoml.h:2933
Definition: cpptoml.h:379
void write(const value< std::string > &v)
Write out a string.
Definition: cpptoml.h:3082
void visit(const table &t, bool in_array=false)
Output a table element of the TOML tree.
Definition: cpptoml.h:2941
static std::string escape_string(const std::string &str)
Escape a string for output.
Definition: cpptoml.h:3030
iterator erase(iterator position)
Erase an element from the array.
Definition: cpptoml.h:972
std::shared_ptr< base > get(const std::string &key) const
Obtains the base for a given key.
Definition: cpptoml.h:1163
A generic base TOML value used for type erasure.
Definition: cpptoml.h:401
STL namespace.
bool contains(const std::string &key) const
Determines if this key table contains the given key.
Definition: cpptoml.h:1144
std::shared_ptr< array > as_array()
Converts the TOML element to an array.
Definition: cpptoml.h:444
std::enable_if< is_one_of< T, int64_t, local_date, local_time, local_datetime, offset_datetime >::value >::type write(const value< T > &v)
Write out an integer, local_date, local_time, local_datetime, or offset_datetime. ...
Definition: cpptoml.h:3110
virtual bool is_table() const
Determines if the given TOML element is a table.
Definition: cpptoml.h:419
void clear()
Clear the array.
Definition: cpptoml.h:827
std::vector< std::shared_ptr< table > >::const_iterator const_iterator
arrays can be iterated over.
Definition: cpptoml.h:916
std::shared_ptr< table_array > get_table_array(const std::string &key) const
Obtains a table_array for a given key, if possible.
Definition: cpptoml.h:1226
Writer that can be passed to accept() functions of cpptoml objects and will output valid TOML to a st...
Definition: cpptoml.h:2916
virtual bool is_array() const override
Determines if the TOML element is an array of "leaf" elements.
Definition: cpptoml.h:622
virtual bool is_array() const
Determines if the TOML element is an array of "leaf" elements.
Definition: cpptoml.h:436
string_to_base_map::const_iterator const_iterator
tables can be iterated over.
Definition: cpptoml.h:1109
Definition: cpptoml.h:94
void write(const value< double > &v)
Write out a double.
Definition: cpptoml.h:3092
Helper object for consuming expected characters.
Definition: cpptoml.h:1569
Exception class for all TOML parsing errors.
Definition: cpptoml.h:1547
value(const T &val)
Constructs a value from the given data.
Definition: cpptoml.h:542
The parser class.
Definition: cpptoml.h:1662
Definition: cpptoml.h:101
std::shared_ptr< table > get_table_qualified(const std::string &key) const
Obtains a table for a given key, if possible.
Definition: cpptoml.h:1196
Represents a TOML keytable.
Definition: cpptoml.h:1093
std::shared_ptr< table > parse()
Parses the stream this parser was created on until EOF.
Definition: cpptoml.h:1679
virtual bool is_value() const
Determines if the given TOML element is a value.
Definition: cpptoml.h:411
Definition: cpptoml.h:898
void push_back(T &&val, typename value_traits< T >::type *=0)
Convenience function for adding a simple element to the end of the array.
Definition: cpptoml.h:770
parser(std::istream &stream)
Parsers are constructed from streams.
Definition: cpptoml.h:1668
void write(const value< bool > &v)
Write out a boolean.
Definition: cpptoml.h:3118
std::shared_ptr< array > get_array(const std::string &key) const
Obtains an array for a given key.
Definition: cpptoml.h:1206
std::shared_ptr< table > get_table(const std::string &key) const
Obtains a table for a given key, if possible.
Definition: cpptoml.h:1185
std::shared_ptr< table_array > get_table_array_qualified(const std::string &key) const
Obtains a table_array for a given key, if possible.
Definition: cpptoml.h:1238
void visit(const table_array &t, bool=false)
Output a table_array element of the TOML tree.
Definition: cpptoml.h:3014
void reserve(size_type n)
Reserve space for n values.
Definition: cpptoml.h:835
std::vector< std::shared_ptr< base > >::const_iterator const_iterator
arrays can be iterated over.
Definition: cpptoml.h:637
Definition: cpptoml.h:115
void accept(Visitor &&visitor, Args &&... args) const
base implementation of accept() that calls visitor.visit() on the concrete class. ...
Definition: cpptoml.h:2885
void erase(const std::string &key)
Removes an element from the table.
Definition: cpptoml.h:1365
Definition: cpptoml.h:275
array_of_trait< T >::return_type get_array_of() const
Obtains a option<vector<T>>.
Definition: cpptoml.h:700
bool contains_qualified(const std::string &key) const
Determines if this key table contains the given key.
Definition: cpptoml.h:1154
iterator erase(iterator position)
Erase an element from the array.
Definition: cpptoml.h:819
toml_writer(std::ostream &s, const std::string &indent_space="\)
Construct a toml_writer that will write to the given stream.
Definition: cpptoml.h:2922
Definition: cpptoml.h:257
void insert(const std::string &key, T &&val, typename value_traits< T >::type *=0)
Convenience shorthand for adding a simple element to the keytable.
Definition: cpptoml.h:1356
iterator insert(iterator position, const std::shared_ptr< table > &value)
Insert a table into the array.
Definition: cpptoml.h:964
array_of_trait< T >::return_type get_array_of(const std::string &key) const
Helper function that attempts to get an array of values of a given type corresponding to the template...
Definition: cpptoml.h:1291
Definition: cpptoml.h:162
Exception class for array insertion errors.
Definition: cpptoml.h:607
void indent()
Indent the proper number of tabs given the size of the path.
Definition: cpptoml.h:3203
bool is_table() const override
Determines if the given TOML element is a table.
Definition: cpptoml.h:1131
std::vector< std::shared_ptr< table > >::iterator iterator
arrays can be iterated over
Definition: cpptoml.h:911
void visit(const array &a, bool=false)
Output an array element of the TOML tree.
Definition: cpptoml.h:2989
string_to_base_map::iterator iterator
tables can be iterated over.
Definition: cpptoml.h:1104
void write_table_header(bool in_array=false)
Write out the header of a table.
Definition: cpptoml.h:3126
void write(const T &v)
Write a value out to the stream.
Definition: cpptoml.h:3213
std::vector< std::shared_ptr< base > > & get()
Obtains the array (vector) of base values.
Definition: cpptoml.h:662
void push_back(const std::shared_ptr< array > &val)
Add an array to the end of the array.
Definition: cpptoml.h:753
Definition: cpptoml.h:109
Definition: cpptoml.h:119
std::shared_ptr< table > parse_file(const std::string &filename)
Utility function to parse a file as a TOML file.
Definition: cpptoml.h:2834
std::vector< std::shared_ptr< base > >::iterator iterator
arrays can be iterated over
Definition: cpptoml.h:632
Definition: cpptoml.h:2850
void write_table_item_header(const base &b)
Write out the identifier for an item in a table.
Definition: cpptoml.h:3174
iterator insert(iterator position, T &&val, typename value_traits< T >::type *=0)
Convenience function for inserting a simple element in the array.
Definition: cpptoml.h:810
std::vector< std::shared_ptr< array > > nested_array() const
Obtains an array of arrays.
Definition: cpptoml.h:720
option< T > get_qualified_as(const std::string &key) const
Helper function that attempts to get a value corresponding to the template parameter from a given key...
Definition: cpptoml.h:1268
A concrete TOML value representing the "leaves" of the "tree".
Definition: cpptoml.h:272
Definition: cpptoml.h:54
virtual bool is_table_array() const
Determines if the given TOML element is an array of tables.
Definition: cpptoml.h:454
void endline()
Write an endline out to the stream.
Definition: cpptoml.h:3222
void clear()
Clear the array.
Definition: cpptoml.h:980
std::vector< std::shared_ptr< value< T > > > array_of() const
Obtains an array of value<T>s.
Definition: cpptoml.h:685
Definition: cpptoml.h:495
Definition: cpptoml.h:38
std::shared_ptr< table > as_table()
Converts the TOML element into a table.
Definition: cpptoml.h:427
option< T > get_as(const std::string &key) const
Helper function that attempts to get a value corresponding to the template parameter from a given key...
Definition: cpptoml.h:1250
void push_back(const std::shared_ptr< table > &val)
Add a table to the end of the array.
Definition: cpptoml.h:956
bool is_value() const override
Determines if the given TOML element is a value.
Definition: cpptoml.h:515