Parsnip
parsing library
Loading...
Searching...
No Matches
parsnip.h
Go to the documentation of this file.
1
10
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
38namespace 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 {
119 DictionaryType *dictionary;
120 ListType *list;
121 StringType *str;
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
188 = typename std::enable_if<!std::is_same<T, bool>::value && std::is_integral<T>::value, int>::type>
189 inline Data (T value) noexcept {
191 data.integer = value;
192 }
193
195 inline Data (double value) noexcept {
196 datatype = Type::Real;
197 data.real = value;
198 }
199
201 inline Data (bool value) noexcept {
202 datatype = Type::Boolean;
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,
273 typename = typename std::enable_if<std::is_same<DataType, std::string>::value>::type>
274 inline const std::string &as() const {
275 return asString();
276 }
277
282 template <typename DataType,
283 typename
284 = typename std::enable_if<!std::is_same<DataType, bool>::value && std::is_integral<DataType>::value>::type>
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
300 template <typename DataType, typename = typename std::enable_if<std::is_floating_point<DataType>::value>::type>
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
311 template <typename DataType, typename = typename std::enable_if<std::is_same<DataType, bool>::value>::type>
312 inline bool as() const {
313 return asBoolean();
314 }
315
318 template <typename DataType, typename = typename std::enable_if<std::is_same<DataType, Data>::value>::type>
319 inline const Data &as() const {
320 return *this;
321 }
322
332 template <typename Container,
333 typename = typename std::enable_if<std::is_class<Container>::value && !std::is_same<Container, Data>::value
334 && !std::is_same<Container, std::string>::value>::type>
335 inline Container as(std::nullptr_t null = nullptr) const {
336 mandateType (Type::List);
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) {
386 mandateType (Type::List);
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
409 template <typename DataType, typename = typename std::enable_if<std::is_trivial<DataType>::value>::type>
410 void foreach (std::function<void (const std::string &, DataType)> func) const {
411 mandateType (Type::Dictionary);
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 {
420 mandateType (Type::Dictionary);
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
452 template <typename DataType, typename = typename std::enable_if<std::is_trivial<Type>::value>::type>
453 void foreach (std::function<void (const DataType)> func) const {
454 mandateType (Type::List);
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 {
463 mandateType (Type::List);
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) {
494 Data data (Type::Dictionary);
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
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
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 &);
606 SchemaBaseRef &operator =(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>;
617 static const Dependencies NoDependencies;
618
619 private:
620 SchemaBaseRef schema;
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
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(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
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
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
const std::string & as() const
Extract data as a string (templated version).
Definition parsnip.h:274
void remove(const std::string &word)
Remove key from dictionary.
Definition parsnip_types.cpp:317
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
Data(std::nullptr_t null=nullptr)
Default constructor.
Definition parsnip.h:167
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
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.
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
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
Data(bool value) noexcept
Boolean constructor.
Definition parsnip.h:201
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
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
static Data make_dictionary(std::initializer_list< std::pair< const char *, const T > > initial_data)
Dictionary factory.
Definition parsnip.h:493
const Data & as() const
Retrieve data as the object itself.
Definition parsnip.h:319
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
Data(Data &&from) noexcept
Move construction.
Definition parsnip.h:224
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
std::vector< Datatype > toList() const
Return a list or a single item in a container.
Definition parsnip.h:382
Data(const std::string &value, bool type_certainty=true)
String constructor, accepting C++ string.
Definition parsnip.h:180
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
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
Exception(const std::string &why)
Construct an exception.
Definition parsnip.h:61
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
The list size does not meet schema requirements.
Definition parsnip.h:575
They key is not specified in the schema.
Definition parsnip.h:583
An element requested from a constant dictionary does not exist.
Definition parsnip.h:554
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
Schema for one JSON object.
Definition parsnip.h:612
Serialization and parsing library.
Definition parsnip.h:38
Data parse_json(std::istream &from, bool check_termination=true)
Parse a stream as JSON data.
Definition parsnip_json.cpp:529
An iterator used to walk items in Data's lists.
Definition parsnip.h:155
Helper functor for schema smart pointers.
Definition parsnip.h:596
Definition parsnip.h:118