diff --git a/include/chaiscript/utility/json.hpp b/include/chaiscript/utility/json.hpp index 22d2f93..294901e 100644 --- a/include/chaiscript/utility/json.hpp +++ b/include/chaiscript/utility/json.hpp @@ -36,19 +36,19 @@ using std::is_floating_point; class JSON { public: - enum class Class { - Null, - Object, - Array, - String, - Floating, - Integral, - Boolean - }; + enum class Class { + Null, + Object, + Array, + String, + Floating, + Integral, + Boolean + }; private: struct Internal { - template + template auto clone(const std::unique_ptr &ptr) { if (ptr != nullptr) { return std::make_unique(*ptr); @@ -57,524 +57,524 @@ class JSON } } - Internal( double d ) : Float( d ), Type(Class::Floating) {} - Internal( long l ) : Int( l ), Type(Class::Integral) {} - Internal( bool b ) : Bool( b ), Type(Class::Boolean) {} - Internal( std::string s ) : String(std::make_unique(std::move(s))), Type(Class::String) {} - Internal() : Type(Class::Null) {} + Internal( double d ) : Float( d ), Type(Class::Floating) {} + Internal( long l ) : Int( l ), Type(Class::Integral) {} + Internal( bool b ) : Bool( b ), Type(Class::Boolean) {} + Internal( std::string s ) : String(std::make_unique(std::move(s))), Type(Class::String) {} + Internal() : Type(Class::Null) {} - Internal(Class t_type) { - set_type(t_type); + Internal(Class t_type) { + set_type(t_type); + } + + Internal(const Internal &other) + : List(clone(other.List)), + Map(clone(other.Map)), + String(clone(other.String)), + Float(other.Float), + Int(other.Int), + Bool(other.Bool), + Type(other.Type) + { + } + + Internal &operator=(const Internal &other) + { + List = clone(other.List); + Map = clone(other.Map); + String = clone(other.String); + Float = other.Float; + Int = other.Int; + Bool = other.Bool; + Type = other.Type; + return *this; + } + + void set_type( Class type ) { + if( type == Type ) { + return; } - Internal(const Internal &other) - : List(clone(other.List)), - Map(clone(other.Map)), - String(clone(other.String)), - Float(other.Float), - Int(other.Int), - Bool(other.Bool), - Type(other.Type) - { + Map.reset(); + List.reset(); + String.reset(); + + switch( type ) { + case Class::Object: Map = std::make_unique>(); break; + case Class::Array: List = std::make_unique>(); break; + case Class::String: String = std::make_unique(); break; + case Class::Floating: Float = 0.0; break; + case Class::Integral: Int = 0; break; + case Class::Boolean: Bool = false; break; + case Class::Null: break; } - Internal &operator=(const Internal &other) - { - List = clone(other.List); - Map = clone(other.Map); - String = clone(other.String); - Float = other.Float; - Int = other.Int; - Bool = other.Bool; - Type = other.Type; - return *this; - } + Type = type; + } - void set_type( Class type ) { - if( type == Type ) { - return; - } + Internal(Internal &&) = default; + Internal &operator=(Internal &&) = default; - Map.reset(); - List.reset(); - String.reset(); + std::unique_ptr> List; + std::unique_ptr> Map; + std::unique_ptr String; + double Float = 0; + long Int = 0; + bool Bool = false; - switch( type ) { - case Class::Object: Map = std::make_unique>(); break; - case Class::Array: List = std::make_unique>(); break; - case Class::String: String = std::make_unique(); break; - case Class::Floating: Float = 0.0; break; - case Class::Integral: Int = 0; break; - case Class::Boolean: Bool = false; break; - case Class::Null: break; - } - - Type = type; - } - - Internal(Internal &&) = default; - Internal &operator=(Internal &&) = default; - - std::unique_ptr> List; - std::unique_ptr> Map; - std::unique_ptr String; - double Float = 0; - long Int = 0; - bool Bool = false; - - Class Type = Class::Null; + Class Type = Class::Null; }; Internal internal; - public: + public: - template - class JSONWrapper { - Container *object = nullptr; + template + class JSONWrapper { + Container *object = nullptr; - public: - JSONWrapper( Container *val ) : object( val ) {} - JSONWrapper( std::nullptr_t ) {} + public: + JSONWrapper( Container *val ) : object( val ) {} + JSONWrapper( std::nullptr_t ) {} - typename Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); } - typename Container::iterator end() { return object ? object->end() : typename Container::iterator(); } - typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); } - typename Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); } - }; + typename Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); } + typename Container::iterator end() { return object ? object->end() : typename Container::iterator(); } + typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); } + typename Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); } + }; - template - class JSONConstWrapper { - const Container *object = nullptr; + template + class JSONConstWrapper { + const Container *object = nullptr; - public: - JSONConstWrapper( const Container *val ) : object( val ) {} - JSONConstWrapper( std::nullptr_t ) {} + public: + JSONConstWrapper( const Container *val ) : object( val ) {} + JSONConstWrapper( std::nullptr_t ) {} - typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::const_iterator(); } - typename Container::const_iterator end() const { return object ? object->end() : typename Container::const_iterator(); } - }; + typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::const_iterator(); } + typename Container::const_iterator end() const { return object ? object->end() : typename Container::const_iterator(); } + }; - JSON() = default; - JSON( std::nullptr_t ) {} + JSON() = default; + JSON( std::nullptr_t ) {} - explicit JSON(Class type) - : internal(type) - { + explicit JSON(Class type) + : internal(type) + { + } + + JSON( initializer_list list ) + : internal(Class::Object) + { + for( auto i = list.begin(), e = list.end(); i != e; ++i, ++i ) { + operator[]( i->to_string() ) = *std::next( i ); + } + } + + template + explicit JSON( T b, typename enable_if::value>::type* = nullptr ) : internal( static_cast(b) ) {} + + template + explicit JSON( T i, typename enable_if::value && !is_same::value>::type* = nullptr ) : internal( static_cast(i) ) {} + + template + explicit JSON( T f, typename enable_if::value>::type* = nullptr ) : internal( static_cast(f) ) {} + + template + explicit JSON( T s, typename enable_if::value>::type* = nullptr ) : internal( static_cast(s) ) {} + + + + static JSON Load( const std::string & ); + + JSON& operator[]( const std::string &key ) { + internal.set_type( Class::Object ); + return internal.Map->operator[]( key ); + } + + JSON& operator[]( const size_t index ) { + internal.set_type( Class::Array ); + if( index >= internal.List->size() ) { + internal.List->resize( index + 1 ); + } + + return internal.List->operator[]( index ); + } + + + JSON &at( const std::string &key ) { + return operator[]( key ); + } + + const JSON &at( const std::string &key ) const { + return internal.Map->at( key ); + } + + JSON &at( size_t index ) { + return operator[]( index ); + } + + const JSON &at( size_t index ) const { + return internal.List->at( index ); + } + + + long length() const { + if( internal.Type == Class::Array ) { + return static_cast(internal.List->size()); + } else { + return -1; + } + } + + bool has_key( const std::string &key ) const { + if( internal.Type == Class::Object ) { + return internal.Map->find( key ) != internal.Map->end(); + } + + return false; + } + + int size() const { + if( internal.Type == Class::Object ) { + return static_cast(internal.Map->size()); + } else if( internal.Type == Class::Array ) { + return static_cast(internal.List->size()); + } else { + return -1; + } + } + + Class JSONType() const { return internal.Type; } + + /// Functions for getting primitives from the JSON object. + bool is_null() const { return internal.Type == Class::Null; } + + std::string to_string() const { bool b; return to_string( b ); } + std::string to_string( bool &ok ) const { + ok = (internal.Type == Class::String); + return ok ? *internal.String : std::string(""); + } + + double to_float() const { bool b; return to_float( b ); } + double to_float( bool &ok ) const { + ok = (internal.Type == Class::Floating); + return ok ? internal.Float : 0.0; + } + + long to_int() const { bool b; return to_int( b ); } + long to_int( bool &ok ) const { + ok = (internal.Type == Class::Integral); + return ok ? internal.Int : 0; + } + + bool to_bool() const { bool b; return to_bool( b ); } + bool to_bool( bool &ok ) const { + ok = (internal.Type == Class::Boolean); + return ok ? internal.Bool : false; + } + + JSONWrapper> object_range() { + if( internal.Type == Class::Object ) { + return JSONWrapper>( internal.Map.get() ); + } else { + return JSONWrapper>( nullptr ); + } + } + + JSONWrapper> array_range() { + if( internal.Type == Class::Array ) { + return JSONWrapper>( internal.List.get() ); + } else { + return JSONWrapper>( nullptr ); + } + } + + JSONConstWrapper> object_range() const { + if( internal.Type == Class::Object ) { + return JSONConstWrapper>( internal.Map.get() ); + } else { + return JSONConstWrapper>( nullptr ); + } + } + + + JSONConstWrapper> array_range() const { + if( internal.Type == Class::Array ) { + return JSONConstWrapper>( internal.List.get() ); + } else { + return JSONConstWrapper>( nullptr ); + } + } + + std::string dump( long depth = 1, std::string tab = " ") const { + switch( internal.Type ) { + case Class::Null: + return "null"; + case Class::Object: { + std::string pad = ""; + for( long i = 0; i < depth; ++i, pad += tab ); + + std::string s = "{\n"; + bool skip = true; + for( auto &p : *internal.Map ) { + if( !skip ) s += ",\n"; + s += ( pad + "\"" + p.first + "\" : " + p.second.dump( depth + 1, tab ) ); + skip = false; + } + s += ( "\n" + pad.erase( 0, 2 ) + "}" ) ; + return s; + } + case Class::Array: { + std::string s = "["; + bool skip = true; + for( auto &p : *internal.List ) { + if( !skip ) s += ", "; + s += p.dump( depth + 1, tab ); + skip = false; + } + s += "]"; + return s; + } + case Class::String: + return "\"" + json_escape( *internal.String ) + "\""; + case Class::Floating: + return std::to_string( internal.Float ); + case Class::Integral: + return std::to_string( internal.Int ); + case Class::Boolean: + return internal.Bool ? "true" : "false"; + } + + throw std::runtime_error("Unhandled JSON type"); + } + + + private: + static std::string json_escape( const std::string &str ) { + std::string output; + for( size_t i = 0; i < str.length(); ++i ) + switch( str[i] ) { + case '\"': output += "\\\""; break; + case '\\': output += "\\\\"; break; + case '\b': output += "\\b"; break; + case '\f': output += "\\f"; break; + case '\n': output += "\\n"; break; + case '\r': output += "\\r"; break; + case '\t': output += "\\t"; break; + default : output += str[i]; break; } - - JSON( initializer_list list ) - : internal(Class::Object) - { - for( auto i = list.begin(), e = list.end(); i != e; ++i, ++i ) { - operator[]( i->to_string() ) = *std::next( i ); - } - } - - template - explicit JSON( T b, typename enable_if::value>::type* = nullptr ) : internal( static_cast(b) ) {} - - template - explicit JSON( T i, typename enable_if::value && !is_same::value>::type* = nullptr ) : internal( static_cast(i) ) {} - - template - explicit JSON( T f, typename enable_if::value>::type* = nullptr ) : internal( static_cast(f) ) {} - - template - explicit JSON( T s, typename enable_if::value>::type* = nullptr ) : internal( static_cast(s) ) {} + return output; + } - - static JSON Load( const std::string & ); - - JSON& operator[]( const std::string &key ) { - internal.set_type( Class::Object ); - return internal.Map->operator[]( key ); - } - - JSON& operator[]( const size_t index ) { - internal.set_type( Class::Array ); - if( index >= internal.List->size() ) { - internal.List->resize( index + 1 ); - } - - return internal.List->operator[]( index ); - } - - - JSON &at( const std::string &key ) { - return operator[]( key ); - } - - const JSON &at( const std::string &key ) const { - return internal.Map->at( key ); - } - - JSON &at( unsigned index ) { - return operator[]( index ); - } - - const JSON &at( unsigned index ) const { - return internal.List->at( index ); - } - - - long length() const { - if( internal.Type == Class::Array ) { - return static_cast(internal.List->size()); - } else { - return -1; - } - } - - bool has_key( const std::string &key ) const { - if( internal.Type == Class::Object ) { - return internal.Map->find( key ) != internal.Map->end(); - } - - return false; - } - - int size() const { - if( internal.Type == Class::Object ) { - return static_cast(internal.Map->size()); - } else if( internal.Type == Class::Array ) { - return static_cast(internal.List->size()); - } else { - return -1; - } - } - - Class JSONType() const { return internal.Type; } - - /// Functions for getting primitives from the JSON object. - bool is_null() const { return internal.Type == Class::Null; } - - std::string to_string() const { bool b; return to_string( b ); } - std::string to_string( bool &ok ) const { - ok = (internal.Type == Class::String); - return ok ? *internal.String : std::string(""); - } - - double to_float() const { bool b; return to_float( b ); } - double to_float( bool &ok ) const { - ok = (internal.Type == Class::Floating); - return ok ? internal.Float : 0.0; - } - - long to_int() const { bool b; return to_int( b ); } - long to_int( bool &ok ) const { - ok = (internal.Type == Class::Integral); - return ok ? internal.Int : 0; - } - - bool to_bool() const { bool b; return to_bool( b ); } - bool to_bool( bool &ok ) const { - ok = (internal.Type == Class::Boolean); - return ok ? internal.Bool : false; - } - - JSONWrapper> object_range() { - if( internal.Type == Class::Object ) { - return JSONWrapper>( internal.Map.get() ); - } else { - return JSONWrapper>( nullptr ); - } - } - - JSONWrapper> array_range() { - if( internal.Type == Class::Array ) { - return JSONWrapper>( internal.List.get() ); - } else { - return JSONWrapper>( nullptr ); - } - } - - JSONConstWrapper> object_range() const { - if( internal.Type == Class::Object ) { - return JSONConstWrapper>( internal.Map.get() ); - } else { - return JSONConstWrapper>( nullptr ); - } - } - - - JSONConstWrapper> array_range() const { - if( internal.Type == Class::Array ) { - return JSONConstWrapper>( internal.List.get() ); - } else { - return JSONConstWrapper>( nullptr ); - } - } - - std::string dump( long depth = 1, std::string tab = " ") const { - switch( internal.Type ) { - case Class::Null: - return "null"; - case Class::Object: { - std::string pad = ""; - for( long i = 0; i < depth; ++i, pad += tab ); - - std::string s = "{\n"; - bool skip = true; - for( auto &p : *internal.Map ) { - if( !skip ) s += ",\n"; - s += ( pad + "\"" + p.first + "\" : " + p.second.dump( depth + 1, tab ) ); - skip = false; - } - s += ( "\n" + pad.erase( 0, 2 ) + "}" ) ; - return s; - } - case Class::Array: { - std::string s = "["; - bool skip = true; - for( auto &p : *internal.List ) { - if( !skip ) s += ", "; - s += p.dump( depth + 1, tab ); - skip = false; - } - s += "]"; - return s; - } - case Class::String: - return "\"" + json_escape( *internal.String ) + "\""; - case Class::Floating: - return std::to_string( internal.Float ); - case Class::Integral: - return std::to_string( internal.Int ); - case Class::Boolean: - return internal.Bool ? "true" : "false"; - } - - throw std::runtime_error("Unhandled JSON type"); - } - - - private: - static std::string json_escape( const std::string &str ) { - std::string output; - for( unsigned i = 0; i < str.length(); ++i ) - switch( str[i] ) { - case '\"': output += "\\\""; break; - case '\\': output += "\\\\"; break; - case '\b': output += "\\b"; break; - case '\f': output += "\\f"; break; - case '\n': output += "\\n"; break; - case '\r': output += "\\r"; break; - case '\t': output += "\\t"; break; - default : output += str[i]; break; - } - return output; - } - - - private: + private: }; struct JSONParser { - static bool isspace(const char c) - { + static bool isspace(const char c) + { #ifdef CHAISCRIPT_MSVC - // MSVC warns on these line in some circumstances + // MSVC warns on these line in some circumstances #pragma warning(push) #pragma warning(disable : 6330) #endif - return ::isspace(c) != 0; + return ::isspace(c) != 0; #ifdef CHAISCRIPT_MSVC #pragma warning(pop) #endif + } + + static void consume_ws( const std::string &str, size_t &offset ) { + while( isspace( str[offset] ) && offset <= str.size() ) ++offset; + } + + static JSON parse_object( const std::string &str, size_t &offset ) { + JSON Object( JSON::Class::Object ); + + ++offset; + consume_ws( str, offset ); + if( str[offset] == '}' ) { + ++offset; return Object; } - static void consume_ws( const std::string &str, size_t &offset ) { - while( isspace( str[offset] ) && offset <= str.size() ) ++offset; + for (;offset= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ) - val += c; - else { - throw std::runtime_error(std::string("JSON ERROR: String: Expected hex character in unicode escape, found '") + c + "'"); - } - } - offset += 4; - } break; - default : val += '\\'; break; - } - } - else - val += c; + return Array; + } + + static JSON parse_string( const std::string &str, size_t &offset ) { + std::string val; + for( char c = str[++offset]; c != '\"' ; c = str[++offset] ) { + if( c == '\\' ) { + switch( str[ ++offset ] ) { + case '\"': val += '\"'; break; + case '\\': val += '\\'; break; + case '/' : val += '/' ; break; + case 'b' : val += '\b'; break; + case 'f' : val += '\f'; break; + case 'n' : val += '\n'; break; + case 'r' : val += '\r'; break; + case 't' : val += '\t'; break; + case 'u' : { + val += "\\u" ; + for( size_t i = 1; i <= 4; ++i ) { + c = str[offset+i]; + if( (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ) + val += c; + else { + throw std::runtime_error(std::string("JSON ERROR: String: Expected hex character in unicode escape, found '") + c + "'"); + } + } + offset += 4; + } break; + default : val += '\\'; break; } - ++offset; - return JSON(val); + } + else + val += c; } + ++offset; + return JSON(val); + } - static JSON parse_number( const std::string &str, size_t &offset ) { - std::string val, exp_str; - char c = '\0'; - bool isDouble = false; - long exp = 0; - for (; offset < str.size() ;) { - c = str[offset++]; - if( (c == '-') || (c >= '0' && c <= '9') ) - val += c; - else if( c == '.' ) { - val += c; - isDouble = true; - } - else - break; - } - if( offset < str.size() && (c == 'E' || c == 'e' )) { - c = str[ offset++ ]; - if( c == '-' ) { exp_str += '-';} - else if( c == '+' ) { } - else --offset; - - for (; offset < str.size() ;) { - c = str[ offset++ ]; - if( c >= '0' && c <= '9' ) - exp_str += c; - else if( !isspace( c ) && c != ',' && c != ']' && c != '}' ) { - throw std::runtime_error(std::string("JSON ERROR: Number: Expected a number for exponent, found '") + c + "'"); - } - else - break; - } - exp = chaiscript::parse_num( exp_str ); - } - else if( offset < str.size() && (!isspace( c ) && c != ',' && c != ']' && c != '}' )) { - throw std::runtime_error(std::string("JSON ERROR: Number: unexpected character '") + c + "'"); - } - --offset; - - if( isDouble ) { - return JSON(chaiscript::parse_num( val ) * std::pow( 10, exp )); - } else { - if( !exp_str.empty() ) { - return JSON(static_cast(chaiscript::parse_num( val )) * std::pow( 10, exp )); - } else { - return JSON(chaiscript::parse_num( val )); - } - } + static JSON parse_number( const std::string &str, size_t &offset ) { + std::string val, exp_str; + char c = '\0'; + bool isDouble = false; + long exp = 0; + for (; offset < str.size() ;) { + c = str[offset++]; + if( (c == '-') || (c >= '0' && c <= '9') ) + val += c; + else if( c == '.' ) { + val += c; + isDouble = true; + } + else + break; } + if( offset < str.size() && (c == 'E' || c == 'e' )) { + c = str[ offset++ ]; + if( c == '-' ) { exp_str += '-';} + else if( c == '+' ) { } + else --offset; - static JSON parse_bool( const std::string &str, size_t &offset ) { - if( str.substr( offset, 4 ) == "true" ) { - offset += 4; - return JSON(true); - } else if( str.substr( offset, 5 ) == "false" ) { - offset += 5; - return JSON(false); - } else { - throw std::runtime_error(std::string("JSON ERROR: Bool: Expected 'true' or 'false', found '") + str.substr( offset, 5 ) + "'"); + for (; offset < str.size() ;) { + c = str[ offset++ ]; + if( c >= '0' && c <= '9' ) + exp_str += c; + else if( !isspace( c ) && c != ',' && c != ']' && c != '}' ) { + throw std::runtime_error(std::string("JSON ERROR: Number: Expected a number for exponent, found '") + c + "'"); } + else + break; + } + exp = chaiscript::parse_num( exp_str ); } + else if( offset < str.size() && (!isspace( c ) && c != ',' && c != ']' && c != '}' )) { + throw std::runtime_error(std::string("JSON ERROR: Number: unexpected character '") + c + "'"); + } + --offset; - static JSON parse_null( const std::string &str, size_t &offset ) { - if( str.substr( offset, 4 ) != "null" ) { - throw std::runtime_error(std::string("JSON ERROR: Null: Expected 'null', found '") + str.substr( offset, 4 ) + "'"); - } - offset += 4; - return JSON(); + if( isDouble ) { + return JSON(chaiscript::parse_num( val ) * std::pow( 10, exp )); + } else { + if( !exp_str.empty() ) { + return JSON(static_cast(chaiscript::parse_num( val )) * std::pow( 10, exp )); + } else { + return JSON(chaiscript::parse_num( val )); + } } + } - static JSON parse_next( const std::string &str, size_t &offset ) { - char value; - consume_ws( str, offset ); - value = str[offset]; - switch( value ) { - case '[' : return parse_array( str, offset ); - case '{' : return parse_object( str, offset ); - case '\"': return parse_string( str, offset ); - case 't' : - case 'f' : return parse_bool( str, offset ); - case 'n' : return parse_null( str, offset ); - default : if( ( value <= '9' && value >= '0' ) || value == '-' ) - return parse_number( str, offset ); - } - throw std::runtime_error(std::string("JSON ERROR: Parse: Unexpected starting character '") + value + "'"); + static JSON parse_bool( const std::string &str, size_t &offset ) { + if( str.substr( offset, 4 ) == "true" ) { + offset += 4; + return JSON(true); + } else if( str.substr( offset, 5 ) == "false" ) { + offset += 5; + return JSON(false); + } else { + throw std::runtime_error(std::string("JSON ERROR: Bool: Expected 'true' or 'false', found '") + str.substr( offset, 5 ) + "'"); } + } + + static JSON parse_null( const std::string &str, size_t &offset ) { + if( str.substr( offset, 4 ) != "null" ) { + throw std::runtime_error(std::string("JSON ERROR: Null: Expected 'null', found '") + str.substr( offset, 4 ) + "'"); + } + offset += 4; + return JSON(); + } + + static JSON parse_next( const std::string &str, size_t &offset ) { + char value; + consume_ws( str, offset ); + value = str[offset]; + switch( value ) { + case '[' : return parse_array( str, offset ); + case '{' : return parse_object( str, offset ); + case '\"': return parse_string( str, offset ); + case 't' : + case 'f' : return parse_bool( str, offset ); + case 'n' : return parse_null( str, offset ); + default : if( ( value <= '9' && value >= '0' ) || value == '-' ) + return parse_number( str, offset ); + } + throw std::runtime_error(std::string("JSON ERROR: Parse: Unexpected starting character '") + value + "'"); + } };