From 0a93d97fdfb09a47e555c9990b4083f0b3eabce7 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 29 Mar 2023 09:16:59 +0200 Subject: Add next_expect*() API to JSON parser --- libbutl/json/parser.cxx | 143 +++++++++++++++++++ libbutl/json/parser.hxx | 299 ++++++++++++++++++++++++++++++++++++++- libbutl/json/parser.ixx | 336 ++++++++++++++++++++++++++++++++++++++++++++ libbutl/json/serializer.hxx | 8 +- 4 files changed, 781 insertions(+), 5 deletions(-) (limited to 'libbutl') diff --git a/libbutl/json/parser.cxx b/libbutl/json/parser.cxx index fa8916b..8ef7422 100644 --- a/libbutl/json/parser.cxx +++ b/libbutl/json/parser.cxx @@ -171,6 +171,149 @@ namespace butl return translate (*peeked_); } + static inline const char* + event_name (event e) + { + switch (e) + { + case event::begin_object: return "beginning of object"; + case event::end_object: return "end of object"; + case event::begin_array: return "beginning of array"; + case event::end_array: return "end of array"; + case event::name: return "member name"; + case event::string: return "string value"; + case event::number: return "numeric value"; + case event::boolean: return "boolean value"; + case event::null: return "null value"; + } + + return ""; + } + + bool parser:: + next_expect (event p, optional s) + { + optional e (next ()); + bool r; + if (e && ((r = *e == p) || (s && *e == *s))) + return r; + + string d ("expected "); + d += event_name (p); + + if (s) + { + d += " or "; + d += event_name (*s); + } + + if (e) + { + d += " instead of "; + d += event_name (*e); + } + + throw invalid_json_input (input_name != nullptr ? input_name : "", + line (), + column (), + position (), + move (d)); + } + + void parser:: + next_expect_name (const char* n, bool su) + { + for (;;) + { + next_expect (event::name); + + if (name () == n) + return; + + if (!su) + break; + + next_expect_value_skip (); + } + + string d ("expected object member name '"); + d += n; + d += "' instead of '"; + d += name (); + d += '\''; + + throw invalid_json_input (input_name != nullptr ? input_name : "", + line (), + column (), + position (), + move (d)); + } + + void parser:: + next_expect_value_skip () + { + optional e (next ()); + + if (e) + { + switch (*e) + { + case event::begin_object: + case event::begin_array: + { + // Skip until matching end_object/array keeping track of nesting. + // We are going to rely on the fact that we should either get such + // an event or next() should throw. + // + event be (*e); + event ee (be == event::begin_object + ? event::end_object + : event::end_array); + + for (size_t n (0);; ) + { + event e (*next ()); + + if (e == ee) + { + if (n == 0) + break; + + --n; + } + else if (e == be) + ++n; + } + + return; + } + case event::string: + case event::number: + case event::boolean: + case event::null: + return; + case event::name: + case event::end_object: + case event::end_array: + break; + } + } + + string d ("expected value"); + + if (e) + { + d += " instead of "; + d += event_name (*e); + } + + throw invalid_json_input (input_name != nullptr ? input_name : "", + line (), + column (), + position (), + move (d)); + } + std::uint64_t parser:: line () const noexcept { diff --git a/libbutl/json/parser.hxx b/libbutl/json/parser.hxx index 241ca46..95d9c4e 100644 --- a/libbutl/json/parser.hxx +++ b/libbutl/json/parser.hxx @@ -172,6 +172,9 @@ namespace butl parser& operator= (parser&&) = delete; parser& operator= (const parser&) = delete; + // Event iteration. + // + // Return the next event or nullopt if end of input is reached. // // In the single-value parsing mode (default) the parsing code could @@ -259,7 +262,8 @@ namespace butl optional peek (); - // Event data. + + // Event data access. // // Return the object member name. @@ -295,6 +299,299 @@ namespace butl std::pair data () const {return std::make_pair (raw_s_, raw_n_);} + + // Higher-level API suitable for parsing specific JSON vocabularies. + // + // The API summary: + // + // void next_expect (event); + // bool next_expect (event primary, event secondary); + // + // void next_expect_name (string name, bool skip_unknown = false); + // + // std::string& next_expect_string (); + // T next_expect_string (); + // std::string& next_expect_number (); + // T next_expect_number (); + // std::string& next_expect_boolean (); + // T next_expect_boolean(); + // + // std::string* next_expect_string_null (); + // optional next_expect_string_null (); + // std::string* next_expect_number_null (); + // optional next_expect_number_null (); + // std::string* next_expect_boolean_null (); + // optional next_expect_boolean_null(); + // + // std::string& next_expect_member_string (string name, bool = false); + // T next_expect_member_string (string name, bool = false); + // std::string& next_expect_member_number (string name, bool = false); + // T next_expect_member_number (string name, bool = false); + // std::string& next_expect_member_boolean (string name, bool = false); + // T next_expect_member_boolean(string name, bool = false); + // + // std::string* next_expect_member_string_null (string, bool = false); + // optional next_expect_member_string_null (string, bool = false); + // std::string* next_expect_member_number_null (string, bool = false); + // optional next_expect_member_number_null (string, bool = false); + // std::string* next_expect_member_boolean_null (string, bool = false); + // optional next_expect_member_boolean_null(string, bool = false); + // + // void next_expect_member_object (string name, bool = false); + // bool next_expect_member_object_null(string name, bool = false); + // + // void next_expect_member_array (string name, bool = false); + // bool next_expect_member_array_null(string name, bool = false); + // + // void next_expect_value_skip(); + + // Get the next event and make sure that it's what's expected: primary + // or, if specified, secondary event. If it is not either, then throw + // invalid_json_input with appropriate description. Return true if it is + // primary. + // + // The secondary expected event is primarily useful for handling + // optional members. For example: + // + // while (p.next_expect (event::name, event::end_object)) + // { + // // Handle object member. + // } + // + // Or homogeneous arrays: + // + // while (p.next_expect (event::string, event::end_array)) + // { + // // Handle array element. + // } + // + // Or values that can be null: + // + // if (p.next_expect (event::begin_object, event::null)) + // { + // // Parse object. + // } + // + bool + next_expect (event primary, optional secondary = nullopt); + + // Get the next event and make sure it is event::name and the object + // member matches the specified name. If either is not, then throw + // invalid_json_input with appropriate description. If skip_unknown is + // true, then skip over unknown member names until a match is found. + // + void + next_expect_name (const char* name, bool skip_unknown = false); + + void + next_expect_name (const std::string&, bool = false); + + // Get the next event and make sure it is event:: returning its + // value similar to the value() functions. If it is not, then throw + // invalid_json_input with appropriate description. + // + std::string& + next_expect_string (); + + template + T + next_expect_string (); + + std::string& + next_expect_number (); + + template + T + next_expect_number (); + + std::string& + next_expect_boolean (); + + template + T + next_expect_boolean (); + + // Similar to next_expect_() but in addition to event:: also + // allow event::null, in which case returning no value. + // + std::string* + next_expect_string_null (); + + template + optional + next_expect_string_null (); + + std::string* + next_expect_number_null (); + + template + optional + next_expect_number_null (); + + std::string* + next_expect_boolean_null (); + + template + optional + next_expect_boolean_null (); + + // Call next_expect_name() followed by next_expect_[_null]() + // returning its result. In other words, parse the entire object member + // with the specifed name and of type , returning its value. + + // next_expect_member_string() + // + std::string& + next_expect_member_string (const char* name, bool skip_unknown = false); + + std::string& + next_expect_member_string (const std::string&, bool = false); + + template + T + next_expect_member_string (const char*, bool = false); + + template + T + next_expect_member_string (const std::string&, bool = false); + + // next_expect_member_number() + // + std::string& + next_expect_member_number (const char* name, bool skip_unknown = false); + + std::string& + next_expect_member_number (const std::string&, bool = false); + + template + T + next_expect_member_number (const char*, bool = false); + + template + T + next_expect_member_number (const std::string&, bool = false); + + // next_expect_member_boolean() + // + std::string& + next_expect_member_boolean (const char* name, bool skip_unknown = false); + + std::string& + next_expect_member_boolean (const std::string&, bool = false); + + template + T + next_expect_member_boolean (const char*, bool = false); + + template + T + next_expect_member_boolean (const std::string&, bool = false); + + // next_expect_member_string_null() + // + std::string* + next_expect_member_string_null (const char*, bool = false); + + std::string* + next_expect_member_string_null (const std::string&, bool = false); + + template + optional + next_expect_member_string_null (const char*, bool = false); + + template + optional + next_expect_member_string_null (const std::string&, bool = false); + + // next_expect_member_number_null() + // + std::string* + next_expect_member_number_null (const char*, bool = false); + + std::string* + next_expect_member_number_null (const std::string&, bool = false); + + template + optional + next_expect_member_number_null (const char*, bool = false); + + template + optional + next_expect_member_number_null (const std::string&, bool = false); + + // next_expect_member_boolean_null() + // + std::string* + next_expect_member_boolean_null (const char*, bool = false); + + std::string* + next_expect_member_boolean_null (const std::string&, bool = false); + + template + optional + next_expect_member_boolean_null (const char*, bool = false); + + template + optional + next_expect_member_boolean_null (const std::string&, bool = false); + + // Call next_expect_name() followed by next_expect(event::begin_object). + // In the _null version also allow event::null, in which case return + // false. + // + void + next_expect_member_object (const char* name, bool skip_unknown = false); + + void + next_expect_member_object (const std::string&, bool = false); + + bool + next_expect_member_object_null (const char*, bool = false); + + bool + next_expect_member_object_null (const std::string&, bool = false); + + // Call next_expect_name() followed by next_expect(event::begin_array). + // In the _null version also allow event::null, in which case return + // false. + // + void + next_expect_member_array (const char* name, bool skip_unknown = false); + + void + next_expect_member_array (const std::string&, bool = false); + + bool + next_expect_member_array_null (const char*, bool = false); + + bool + next_expect_member_array_null (const std::string&, bool = false); + + // Get the next event and make sure it is the beginning of a value + // (begin_object, begin_array, string, number, boolean, null). If it is + // not, then throw invalid_json_input with appropriate description. + // Otherwise, skip until the end of the value, recursively in case of + // object and array. + // + // This function is primarily useful for skipping unknown object + // members, for example: + // + // while (p.next_expect (event::name, event::end_object)) + // { + // if (p.name () == "known") + // { + // // Handle known member. + // } + // else + // p.next_expect_value_skip (); + // } + // + void + next_expect_value_skip (); + + // Parsing location. + // + // Return the line number (1-based) corresponding to the most recently // parsed event or 0 if nothing has been parsed yet. // diff --git a/libbutl/json/parser.ixx b/libbutl/json/parser.ixx index 3f02a1e..cf6dca3 100644 --- a/libbutl/json/parser.ixx +++ b/libbutl/json/parser.ixx @@ -212,5 +212,341 @@ namespace butl return parse_value (value_.data (), value_.size (), *this); } + + inline void parser:: + next_expect_name (const std::string& n, bool su) + { + next_expect_name (n.c_str (), su); + } + + // next_expect_() + // + inline std::string& parser:: + next_expect_string () + { + next_expect (event::string); + return value (); + } + + template + inline T parser:: + next_expect_string () + { + next_expect (event::string); + return value (); + } + + inline std::string& parser:: + next_expect_number () + { + next_expect (event::number); + return value (); + } + + template + inline T parser:: + next_expect_number () + { + next_expect (event::number); + return value (); + } + + inline std::string& parser:: + next_expect_boolean () + { + next_expect (event::boolean); + return value (); + } + + template + inline T parser:: + next_expect_boolean () + { + next_expect (event::boolean); + return value (); + } + + // next_expect__null() + // + inline std::string* parser:: + next_expect_string_null () + { + return next_expect (event::string, event::null) ? &value () : nullptr; + } + + template + inline optional parser:: + next_expect_string_null () + { + return next_expect (event::string, event::null) + ? optional (value ()) + : nullopt; + } + + inline std::string* parser:: + next_expect_number_null () + { + return next_expect (event::number, event::null) ? &value () : nullptr; + } + + template + inline optional parser:: + next_expect_number_null () + { + return next_expect (event::number, event::null) + ? optional (value ()) + : nullopt; + } + + inline std::string* parser:: + next_expect_boolean_null () + { + return next_expect (event::boolean, event::null) ? &value () : nullptr; + } + + template + inline optional parser:: + next_expect_boolean_null () + { + return next_expect (event::boolean, event::null) + ? optional (value ()) + : nullopt; + } + + // next_expect_member_string() + // + inline std::string& parser:: + next_expect_member_string (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_string (); + } + + inline std::string& parser:: + next_expect_member_string (const std::string& n, bool su) + { + return next_expect_member_string (n.c_str (), su); + } + + template + inline T parser:: + next_expect_member_string (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_string (); + } + + template + inline T parser:: + next_expect_member_string (const std::string& n, bool su) + { + return next_expect_member_string (n.c_str (), su); + } + + // next_expect_member_number() + // + inline std::string& parser:: + next_expect_member_number (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_number (); + } + + inline std::string& parser:: + next_expect_member_number (const std::string& n, bool su) + { + return next_expect_member_number (n.c_str (), su); + } + + template + inline T parser:: + next_expect_member_number (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_number (); + } + + template + inline T parser:: + next_expect_member_number (const std::string& n, bool su) + { + return next_expect_member_number (n.c_str (), su); + } + + // next_expect_member_boolean() + // + inline std::string& parser:: + next_expect_member_boolean (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_boolean (); + } + + inline std::string& parser:: + next_expect_member_boolean (const std::string& n, bool su) + { + return next_expect_member_boolean (n.c_str (), su); + } + + template + inline T parser:: + next_expect_member_boolean (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_boolean (); + } + + template + inline T parser:: + next_expect_member_boolean (const std::string& n, bool su) + { + return next_expect_member_boolean (n.c_str (), su); + } + + // next_expect_member_string_null() + // + inline std::string* parser:: + next_expect_member_string_null (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_string_null (); + } + + inline std::string* parser:: + next_expect_member_string_null (const std::string& n, bool su) + { + return next_expect_member_string_null (n.c_str (), su); + } + + template + inline optional parser:: + next_expect_member_string_null (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_string_null (); + } + + template + inline optional parser:: + next_expect_member_string_null (const std::string& n, bool su) + { + return next_expect_member_string_null (n.c_str (), su); + } + + // next_expect_member_number_null() + // + inline std::string* parser:: + next_expect_member_number_null (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_number_null (); + } + + inline std::string* parser:: + next_expect_member_number_null (const std::string& n, bool su) + { + return next_expect_member_number_null (n.c_str (), su); + } + + template + inline optional parser:: + next_expect_member_number_null (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_number_null (); + } + + template + inline optional parser:: + next_expect_member_number_null (const std::string& n, bool su) + { + return next_expect_member_number_null (n.c_str (), su); + } + + // next_expect_member_boolean_null() + // + inline std::string* parser:: + next_expect_member_boolean_null (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_boolean_null (); + } + + inline std::string* parser:: + next_expect_member_boolean_null (const std::string& n, bool su) + { + return next_expect_member_boolean_null (n.c_str (), su); + } + + template + inline optional parser:: + next_expect_member_boolean_null (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect_boolean_null (); + } + + template + inline optional parser:: + next_expect_member_boolean_null (const std::string& n, bool su) + { + return next_expect_member_boolean_null (n.c_str (), su); + } + + // next_expect_member_object[_null]() + // + inline void parser:: + next_expect_member_object (const char* n, bool su) + { + next_expect_name (n, su); + next_expect (event::begin_object); + } + + inline void parser:: + next_expect_member_object (const std::string& n, bool su) + { + next_expect_member_object (n.c_str (), su); + } + + inline bool parser:: + next_expect_member_object_null (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect (event::begin_object, event::null); + } + + inline bool parser:: + next_expect_member_object_null (const std::string& n, bool su) + { + return next_expect_member_object_null (n.c_str (), su); + } + + // next_expect_member_array[_null]() + // + inline void parser:: + next_expect_member_array (const char* n, bool su) + { + next_expect_name (n, su); + next_expect (event::begin_array); + } + + inline void parser:: + next_expect_member_array (const std::string& n, bool su) + { + next_expect_member_array (n.c_str (), su); + } + + inline bool parser:: + next_expect_member_array_null (const char* n, bool su) + { + next_expect_name (n, su); + return next_expect (event::begin_array, event::null); + } + + inline bool parser:: + next_expect_member_array_null (const std::string& n, bool su) + { + return next_expect_member_array_null (n.c_str (), su); + } } } diff --git a/libbutl/json/serializer.hxx b/libbutl/json/serializer.hxx index c143dab..5192cb4 100644 --- a/libbutl/json/serializer.hxx +++ b/libbutl/json/serializer.hxx @@ -169,8 +169,8 @@ namespace butl // // The member_begin_object() version is a shortcut for: // - // member_name (name, check); - // begin_object (); + // member_name (name, check); + // begin_object (); // void begin_object (); @@ -212,8 +212,8 @@ namespace butl // // The member_begin_array() version is a shortcut for: // - // member_name (name, check); - // begin_array (); + // member_name (name, check); + // begin_array (); // void begin_array (); -- cgit v1.1