pianod2
multisource multiuser scriptable networked music player
parsnip.h
Go to the documentation of this file.
1 
11 #pragma once
12 
13 #include <config.h>
14 
15 #include <exception>
16 #include <limits>
17 #include <memory>
18 #include <istream>
19 #include <ostream>
20 #include <iostream>
21 #include <string>
22 #include <type_traits>
23 #include <unordered_map>
24 #include <vector>
25 #include <set>
26 #include <functional>
27 #ifdef PARSNIP_DICTIONARY_SORTED
28 #include <map>
29 #endif
30 
31 #include <cassert>
32 #include <climits>
33 #include <cmath>
34 #include <cstdlib>
35 #include <cstring>
36 
38 namespace Parsnip {
39 
41  class Exception : public std::exception {
42  private:
43  std::string location;
44  protected:
45  std::string reason;
46 
47 
48  public:
49  virtual const char *what() const noexcept override {
50  return reason.c_str();
51  };
55  inline const char *where () const noexcept {
56  return location.empty() ? nullptr : location.c_str();
57  }
58  void addBacktraceLocation (const std::string &);
59  protected:
61  Exception (const std::string &why) : reason (why){};
62 
66  Exception (const std::string &why, const std::string &detail) : reason (why) {
67  reason += ": ";
68  reason += detail;
69  };
70  };
71 
73  class DataRangeError : public Exception {
74  public:
75  inline DataRangeError() : Exception ("Value out of representable range"){};
76  inline DataRangeError (const std::string &value) : Exception ("Value exceeds datatype range", value){};
77  };
78 
81  class Data {
82  friend class IncorrectDataType;
83  friend class SchemaBase;
84  friend class DictionarySchema;
85  friend class ConjunctionSchema;
86 
87  public:
88  static const struct dictionary_t {
90  static const struct list_t {
91  } List;
92  static constexpr bool Flexible{false};
93  static constexpr int NoIndent{-32767};
94 
95  using StringType = std::string; // As long as everything is UTF-8 encoded anyway...
96  using ListType = std::vector<Data>;
97 
99  enum class Type {
100  Null,
101  Dictionary,
102  List,
103  String,
105  Integer,
106  Real,
107  Boolean
108  };
109 
110  protected:
111 #ifdef PARSNIP_DICTIONARY_SORTED
112  using DictionaryType = std::map<std::string, Data>;
113 #else
114  using DictionaryType = std::unordered_map<std::string, Data>;
115 #endif
116 
118  union data_t {
122  long integer;
123  double real;
124  bool boolean;
125  } data;
126 
127  void release();
128 
129  private:
130  Data (Type kind);
131  void mandateType (Type type) const;
132 
133  inline void loadDictionary(){};
134 
136  template <typename... More>
137  void loadDictionary (const char *name, Data &&value, More &&... remainder) {
138  (*(data.dictionary))[name] = std::move (value);
139  loadDictionary (std::forward<More> (remainder)...);
140  }
141 
142  inline void loadArray(){};
143 
145  template <typename... More>
146  void loadArray (Data &&value, More... remainder) {
147  data.list->push_back (std::move (value));
148  loadArray (std::forward<More> (remainder)...);
149  }
150 
151  public:
152  using size_type = ListType::size_type;
153 
155  struct iterator : std::iterator<std::forward_iterator_tag, Data> {
156  const ListType *list = nullptr;
157  ListType::size_type position = 0;
158  iterator (const Data *l);
159  iterator (const Data *l, size_type pos);
160  const Data &operator*() const noexcept;
161  iterator &operator++() noexcept;
162  iterator operator++ (int) noexcept;
163  bool operator!= (const iterator &compare) const noexcept;
164  };
165 
167  Data (std::nullptr_t null = nullptr) {
168  // Do nothing
169  }
170 
174  Data (const char *value, bool type_certainty = true) {
175  data.str = new StringType (value);
176  datatype = type_certainty ? Type::String : Type::FlexibleString;
177  }
178 
180  inline Data (const std::string &value, bool type_certainty = true) {
181  data.str = new StringType (value);
182  datatype = type_certainty ? Type::String : Type::FlexibleString;
183  }
184 
186  template <typename T,
187  typename
189  inline Data (T value) noexcept {
191  data.integer = value;
192  }
193 
195  inline Data (double value) noexcept {
197  data.real = value;
198  }
199 
201  inline Data (bool value) noexcept {
203  data.boolean = value;
204  }
205 
210  template <typename... T>
211  Data (dictionary_t, T... params) : Data (Type::Dictionary) {
212  loadDictionary (std::forward<T> (params)...);
213  }
214 
218  template <typename... T>
219  Data (list_t, T... params) : Data (Type::List) {
220  loadArray (std::forward<T> (params)...);
221  }
222 
224  inline Data (Data &&from) noexcept {
225  datatype = from.datatype;
226  data = from.data;
227  from.datatype = Type::Null;
228  }
229 
231  inline Data &operator= (Data &&from) noexcept {
232  if (this != &from) {
233  release();
234  datatype = from.datatype;
235  data = from.data;
236  from.datatype = Type::Null;
237  }
238  return *this;
239  }
240 
241  // Copy constructor & assignment
242  Data (const Data &from);
243  Data &operator= (const Data &from);
244 
245  inline ~Data() {
246  release();
247  }
248 
249  bool operator== (const Data &compare) const;
250  inline bool operator!= (const Data &compare) const {
251  return !operator== (compare);
252  }
253 
254  const Data &makeFlexible () const;
255 
258  inline bool isNull (void) const noexcept {
259  return (datatype == Type::Null);
260  }
261 
262  // Retrieve values by asking for type directly.
263  const StringType &asString() const;
264  long asLong (int base = 0) const;
265  int asInteger (int base = 0) const;
266  double asDouble() const;
267  bool asBoolean() const;
268  ListType asList() const;
269 
272  template <typename DataType,
274  inline const std::string &as() const {
275  return asString();
276  }
277 
282  template <typename DataType,
283  typename
285  inline DataType as (int base = 0) const {
286  long result = asLong (base);
287 #pragma GCC diagnostic push
288 #pragma GCC diagnostic ignored "-Wsign-compare"
289  if (result < std::numeric_limits<DataType>::lowest() || result > std::numeric_limits<DataType>::max()) {
290 #pragma GCC diagnostic pop
291  throw DataRangeError (std::to_string (result));
292  }
293  return result;
294  }
295 
301  inline DataType as() const {
302  double result = asDouble();
303  if (result < std::numeric_limits<DataType>::lowest() || result > std::numeric_limits<DataType>::max()) {
304  throw DataRangeError (std::to_string (result));
305  }
306  return result;
307  }
308 
312  inline bool as() const {
313  return asBoolean();
314  }
315 
319  inline const Data &as() const {
320  return *this;
321  }
322 
332  template <typename Container,
335  inline Container as(std::nullptr_t null = nullptr) const {
337  Container result;
338  for (const auto &item : *(data.list)) {
339  result.push_back (item.as <typename Container::value_type>());
340  }
341  return result;
342  }
343 
350  template <typename Datatype>
351  inline Datatype getOr (const std::string &name, const Datatype &defvalue) const {
352  return (contains (name) ? (*this)[name].as<Datatype>() : defvalue);
353  }
354 
358  inline StringType getOr (const std::string &name, const char *defvalue) const {
359  return (contains (name) ? (*this)[name].asString() : StringType (defvalue));
360  }
361 
367  inline Data getOr (const std::string &name, const Data &defvalue) const {
368  return (contains (name) ? (*this)[name] : defvalue);
369  }
370 
381  template <typename Datatype>
382  inline std::vector<Datatype> toList () const {
383  if (datatype == Type::List) {
384  return as<std::vector <Datatype>> ();
385  } else if (datatype == Type::Dictionary) {
387  }
388  std::vector<Datatype> results;
389  results.push_back (as <Datatype>());
390  return results;
391  }
392 
393  // Dictionary accessors
394  const Data &operator[] (const std::string &word) const;
395  Data &operator[] (const std::string &word);
396  bool contains (const std::string &word) const;
397  void remove (const std::string &word);
398  inline const Data &at (const std::string &word) const {
399  return (*this) [word];
400  }
401 
410  void foreach (std::function<void (const std::string &, DataType)> func) const {
412  for (const auto &it : *(data.dictionary)) {
413  func (it.first, it.second.as<DataType>());
414  }
415  }
416 
418  template <typename DataType>
419  void foreach (std::function<void (const std::string &, const DataType &)> func) const {
421  for (const auto &it : *(data.dictionary)) {
422  func (it.first, it.second.as<DataType>());
423  }
424  }
425 
426  // List accessors
427  const Data &operator[] (size_type index) const;
428  Data &operator[] (size_type index);
429  void remove (size_type index);
430  void push_back (const Data &value);
431  void push_back (Data &&value);
432  size_type size() const;
433 
438  inline bool empty () const {
439  return (datatype == Type::Null ||
440  (datatype == Type::List && size() == 0) ||
441  (datatype == Type::Dictionary && (*data.dictionary).empty()));
442 
443  }
444 
453  void foreach (std::function<void (const DataType)> func) const {
455  for (const auto &it : *(data.list)) {
456  func (it.as<DataType>());
457  }
458  }
459 
461  template <typename DataType>
462  void foreach (std::function<void (const DataType &)> func) const {
464  for (const auto &it : *(data.list)) {
465  func (it.as<DataType>());
466  }
467  }
468 
472  inline Data::iterator begin() const {
473  return iterator{this};
474  }
475 
479  inline Data::iterator end() const {
480  iterator it{this, size()};
481  return it;
482  }
483 
484  static const char *type_name (Type ty);
485 
492  template <typename T = Data>
493  static Data make_dictionary (std::initializer_list <std::pair <const char *, const T>> initial_data) {
495  for (auto &it : initial_data) {
496  data [it.first] = it.second;
497  }
498  return data;
499  }
500 
503  template <typename... T>
504  static Data make_list(T&&... params) {
505  Data data (Type::List);
506  data.loadArray (std::forward<T> (params)...);
507  return data;
508  }
509 
510  public:
511  std::string toJson (int indent = NoIndent) const;
512  std::ostream &toJson (std::ostream &target, int indent = NoIndent, bool suppress = false) const;
513  std::ostream &dumpJson (const std::string &intro, std::ostream &target = std::clog) const;
514  };
515 
516  Data parse_json (std::istream &from, bool check_termination = true);
517  Data parse_json (const std::string &from);
518 
519 
520 #ifdef PARSNIP_JSON_TRACK_POSITION
521  class StreamPositionCounter;
522  using InputStream = StreamPositionCounter;
523 #else
524  using InputStream = std::istream;
525 #endif
526 
528  class DataFormatError : public Exception {
529 #ifdef PARSNIP_JSON_TRACK_POSITION
530  public:
531  struct Location {
532  unsigned line = 0;
533  unsigned character = 0;
534  Location () = default;
535  Location (const Location &) = default;
536  Location &operator =(const Location &) = default;
537  Location (unsigned l, unsigned c): line (l), character (c) {};
538  };
539  private:
540  const Location error_location {};
541  public:
544  inline Location where() const {
545  return error_location;
546  }
547 #endif
548  public:
549  inline DataFormatError (const std::string &detail): Exception ("Invalid data format", detail) {};
550  DataFormatError (const InputStream &loc, const std::string &detail);
551  };
552 
554  class NoSuchKey : public Exception {
555  public:
556  inline NoSuchKey (const std::string &key) : Exception ("No such key", key){};
557  };
558 
560  class IncorrectDataType : public Exception {
561  public:
562  IncorrectDataType (Data::Type expected, Data::Type actual) : Exception ("Incorrect data type", "Expected: ") {
563  reason += Data::type_name (expected);
564  reason += ", Actual: ";
565  reason += Data::type_name (actual);
566  }
567  };
568 
569  inline std::ostream &operator<< (std::ostream &out, const Parsnip::Data &data) {
570  data.toJson (out);
571  return out;
572  }
573 
575  class IncorrectListSize : public Exception {
576  public:
577  inline IncorrectListSize () : Exception ("Wrong list size") {};
578  inline IncorrectListSize (int size, int min, int max) :
579  Exception ("wrong list size", std::to_string (size) + " (expected " + std::to_string (min) + "-" + std::to_string (max) + "") {};
580  };
581 
583  class InvalidKey : public Exception {
584  public:
585  inline InvalidKey (const std::string &key) : Exception ("invalid key", key) {};
586  };
587 
589  class SchemaConflict : public Exception {
590  public:
591  inline SchemaConflict (const std::string &reason) : Exception ("schema conflict", reason) {};
592  SchemaConflict (const std::string &reason, const class SchemaBase &from, const class SchemaBase &into);
593  };
594 
597  void operator () (class SchemaBase *free_me);
598  };
599 
601  class SchemaBaseRef: public std::unique_ptr<class SchemaBase, SchemaBaseDeleter> {
602  public:
603  using std::unique_ptr<class SchemaBase, SchemaBaseDeleter>::unique_ptr;
604  SchemaBaseRef() = default;
605  SchemaBaseRef (const SchemaBaseRef &);
607  };
608 
609  using SchemaRef = std::shared_ptr <class Schema>;
610 
612  class Schema {
613  friend class OptionSchema;
614  public:
615  using OptionSchemas = std::unordered_map <class OptionParser *, SchemaRef>;
616  using Dependencies = std::set <std::string>;
618 
619  private:
621  void integrateSchema (const int, const class DictionarySchema &);
622  std::ostream &dump (std::ostream &target, int indent, bool suppress_indent) const;
623 
624  Schema (SchemaBase *from);
625  public:
626  Schema () = default;
627  Schema (const class OptionParser &from, const OptionSchemas &option_schemas);
628  Schema (Schema &&) = default;
629  Schema &operator =(Schema &&) = default;
630  Schema (const Parsnip::Data &schema_spec);
631 
632  void addMember (const char *name, const SchemaBase &new_schema, bool mandatory = false,
633  const Dependencies &dependencies = NoDependencies);
634  void replaceMember (const char *name, const SchemaBase &new_schema);
635  void removeMember (const char *name);
636 
637  void validate (const Data &) const;
638  std::ostream &dump (const std::string &intro, std::ostream &target = std::clog) const;
639  };
640 
641 } // namespace Parsnip
A meta-schema that determines if any, all, or exactly one or none of its subschemas are valid.
Definition: parsnip_schema.h:358
DataFormatError represents a syntax error in a datastream.
Definition: parsnip.h:528
DataFormatError(const std::string &detail)
Definition: parsnip.h:549
Generic data type.
Definition: parsnip.h:81
static Data make_list(T &&... params)
List factory.
Definition: parsnip.h:504
bool as() const
Extract data as a boolean.
Definition: parsnip.h:312
DataType as() const
Extract data as a floating point type.
Definition: parsnip.h:301
~Data()
Definition: parsnip.h:245
Data(list_t, T... params)
List constructor.
Definition: parsnip.h:219
int asInteger(int base=0) const
Return value of data as an integer.
Definition: parsnip_types.cpp:267
std::unordered_map< std::string, Data > DictionaryType
Definition: parsnip.h:114
Data(dictionary_t, T... params)
Dictionary constructor.
Definition: parsnip.h:211
Datatype getOr(const std::string &name, const Datatype &defvalue) const
Extract a value if a member exists, otherwise use a default.
Definition: parsnip.h:351
static constexpr int NoIndent
Prevent indentation.
Definition: parsnip.h:93
void loadDictionary()
Definition: parsnip.h:133
static const struct Parsnip::Data::dictionary_t Dictionary
Dictionary flag type/value.
const Data & operator[](const std::string &word) const
Dictionary member accessor.
Definition: parsnip_types.cpp:300
void release()
Release any stored data and restore to an "empty" state.
Definition: parsnip_types.cpp:112
bool empty() const
Empty check.
Definition: parsnip.h:438
void remove(const std::string &word)
Remove key from dictionary.
Definition: parsnip_types.cpp:317
union Parsnip::Data::data_t data
void loadArray(Data &&value, More... remainder)
List constructor helper function, inserting a moved Data.
Definition: parsnip.h:146
ListType asList() const
Return data as a list.
Definition: parsnip_types.cpp:280
double asDouble() const
Return value of data as a double. If the data is a string, parse it as a float.
Definition: parsnip_types.cpp:189
const Data & as() const
Retrieve data as the object itself.
Definition: parsnip.h:319
bool contains(const std::string &word) const
Check if a key exists in a dictionary.
Definition: parsnip_types.cpp:328
bool operator==(const Data &compare) const
Compare two structures.
Definition: parsnip_types.cpp:143
Data(Type kind)
Definition: parsnip_types.cpp:52
Type
Types that may be stored in the data.
Definition: parsnip.h:99
@ String
The value is string, even if the string contains a number.
@ List
Dictionary keyed by string.
@ Integer
Value is an integer but may also be accessed as a real.
@ Null
Nothing is present.
@ FlexibleString
The value is a string, but may convert to a number if the value is numeric.
const std::string & as() const
Extract data as a string (templated version).
Definition: parsnip.h:274
StringType getOr(const std::string &name, const char *defvalue) const
Specialization of 'getOr' that accepts a const char * default; otherwise, overload resolution chooses...
Definition: parsnip.h:358
bool isNull(void) const noexcept
Check if data contains a value.
Definition: parsnip.h:258
std::ostream & dumpJson(const std::string &intro, std::ostream &target=std::clog) const
Definition: parsnip_json.cpp:141
static Data make_dictionary(std::initializer_list< std::pair< const char *, const T >> initial_data)
Dictionary factory.
Definition: parsnip.h:493
static const char * type_name(Type ty)
Return the name corresponding to a type.
Definition: parsnip_types.cpp:385
std::string toJson(int indent=NoIndent) const
Return serial data encoded using JSON.
Definition: parsnip_json.cpp:150
Data & operator=(Data &&from) noexcept
Move assignment.
Definition: parsnip.h:231
std::string StringType
Definition: parsnip.h:95
Data(bool value) noexcept
Boolean constructor.
Definition: parsnip.h:201
void mandateType(Type type) const
Definition: parsnip_types.cpp:30
DataType as(int base=0) const
Extract data as an integer type, except boolean.
Definition: parsnip.h:285
Data(T value) noexcept
Integer constructor.
Definition: parsnip.h:189
const StringType & asString() const
Retrieve string value.
Definition: parsnip_types.cpp:133
Data(double value) noexcept
Floating point constructor.
Definition: parsnip.h:195
Data::iterator end() const
Retrieve end iterator.
Definition: parsnip.h:479
ListType::size_type size_type
Definition: parsnip.h:152
Container as(std::nullptr_t null=nullptr) const
Return contents of a list as a specific datatype.
Definition: parsnip.h:335
size_type size() const
Retrieve number of entries in a list.
Definition: parsnip_types.cpp:377
void loadArray()
Definition: parsnip.h:142
const Data & at(const std::string &word) const
Definition: parsnip.h:398
bool asBoolean() const
Return value of data as a boolean. If the data is a string, translate "true" and "false".
Definition: parsnip_types.cpp:217
Type datatype
Type presently stored in object.
Definition: parsnip.h:117
static constexpr bool Flexible
Flag to make strings flexible.
Definition: parsnip.h:92
Data(const char *value, bool type_certainty=true)
String constructor.
Definition: parsnip.h:174
std::vector< Data > ListType
Definition: parsnip.h:96
Data(Data &&from) noexcept
Move construction.
Definition: parsnip.h:224
std::vector< Datatype > toList() const
Return a list or a single item in a container.
Definition: parsnip.h:382
Data getOr(const std::string &name, const Data &defvalue) const
Specialization of getOr for Data.
Definition: parsnip.h:367
static const struct Parsnip::Data::list_t List
List flag type/value.
Data::iterator begin() const
Retrieve forward iterator.
Definition: parsnip.h:472
const Data & makeFlexible() const
IF a data item is a string, change it to a flexible string.
Definition: parsnip_types.cpp:41
Data(const std::string &value, bool type_certainty=true)
String constructor, accepting C++ string.
Definition: parsnip.h:180
void loadDictionary(const char *name, Data &&value, More &&... remainder)
Dictionary constructor helper function, inserting a moved Data.
Definition: parsnip.h:137
void push_back(const Data &value)
List back-inserter.
Definition: parsnip_types.cpp:363
long asLong(int base=0) const
Return value of data as a long integer.
Definition: parsnip_types.cpp:238
DataRangeError indicates a value received in a stream can't be represented.
Definition: parsnip.h:73
DataRangeError()
Definition: parsnip.h:75
DataRangeError(const std::string &value)
Definition: parsnip.h:76
Schema component for validating dictionaries.
Definition: parsnip_schema.h:307
Class representing issues related to serialization & parsing.
Definition: parsnip.h:41
void addBacktraceLocation(const std::string &)
Insert a new portion of the exception location at the front.
Definition: parsnip_types.cpp:23
std::string location
Storage for the exception location.
Definition: parsnip.h:43
Exception(const std::string &why)
Construct an exception.
Definition: parsnip.h:61
virtual const char * what() const noexcept override
Definition: parsnip.h:49
const char * where() const noexcept
Retrieve the exception location information.
Definition: parsnip.h:55
Exception(const std::string &why, const std::string &detail)
Construct an exception.
Definition: parsnip.h:66
std::string reason
Storage for the exception explanation.
Definition: parsnip.h:45
The data's type is inconsistent with the use requested of it.
Definition: parsnip.h:560
IncorrectDataType(Data::Type expected, Data::Type actual)
Definition: parsnip.h:562
The list size does not meet schema requirements.
Definition: parsnip.h:575
IncorrectListSize(int size, int min, int max)
Definition: parsnip.h:578
IncorrectListSize()
Definition: parsnip.h:577
They key is not specified in the schema.
Definition: parsnip.h:583
InvalidKey(const std::string &key)
Definition: parsnip.h:585
An element requested from a constant dictionary does not exist.
Definition: parsnip.h:554
NoSuchKey(const std::string &key)
Definition: parsnip.h:556
A class for parsing command line options (name-value pairs, for instance).
Definition: parsnip_command.h:82
Schema adapter component that allows use of schemas from option parsers.
Definition: parsnip_schema.h:258
Definition: parsnip_schema.h:28
Smart pointer for schemas, with unusual ability to be copied.
Definition: parsnip.h:601
Parsing/command patterns produce schema inconsistency.
Definition: parsnip.h:589
SchemaConflict(const std::string &reason)
Definition: parsnip.h:591
Schema for one JSON object.
Definition: parsnip.h:612
Schema(Schema &&)=default
static const Dependencies NoDependencies
Definition: parsnip.h:617
std::set< std::string > Dependencies
Definition: parsnip.h:616
std::unordered_map< class OptionParser *, SchemaRef > OptionSchemas
Definition: parsnip.h:615
Schema(const class OptionParser &from, const OptionSchemas &option_schemas)
SchemaBaseRef schema
Definition: parsnip.h:620
Schema()=default
uint32_t value
Definition: audiooutput.cpp:68
void validate(const AudioSettings settings)
Validate audio settings.
Definition: audiooptionsparser.cpp:33
Serialization and parsing library.
Definition: mediaunit.h:26
Data parse_json(std::istream &from, bool check_termination=true)
Parse a stream as JSON data.
Definition: parsnip_json.cpp:529
std::istream InputStream
Definition: parsnip.h:524
std::ostream & operator<<(std::ostream &out, const Parsnip::Data &data)
Definition: parsnip.h:569
std::shared_ptr< class Schema > SchemaRef
Definition: parsnip.h:609
Definition: parsnip.h:88
An iterator used to walk items in Data's lists.
Definition: parsnip.h:155
const ListType * list
Definition: parsnip.h:156
bool operator!=(const iterator &compare) const noexcept
Definition: parsnip_iterator.cpp:47
const Data & operator*() const noexcept
Definition: parsnip_iterator.cpp:29
iterator(const Data *l)
Definition: parsnip_iterator.cpp:16
ListType::size_type position
Definition: parsnip.h:157
Definition: parsnip.h:90
Helper functor for schema smart pointers.
Definition: parsnip.h:596
Definition: parsnip.h:118
ListType * list
Definition: parsnip.h:120
double real
Definition: parsnip.h:123
long integer
Definition: parsnip.h:122
bool boolean
Definition: parsnip.h:124
StringType * str
Definition: parsnip.h:121
DictionaryType * dictionary
Definition: parsnip.h:119