diff --git a/benchmark/json.cc b/benchmark/json.cc index b11b4b856..cb137ca86 100644 --- a/benchmark/json.cc +++ b/benchmark/json.cc @@ -207,6 +207,121 @@ static void JSON_Parse_Real(benchmark::State &state) { } } +static void JSON_Parse_Decimal(benchmark::State &state) { + const auto document{R"JSON([ + 123456789012345678901234567890, + 987654321098765432109876543210, + 111111111111111111111111111111, + 999999999999999999999999999999, + 555555555555555555555555555555, + 1e309, + 1e310, + 1e320, + 1e350, + 1e400, + 9.99e308, + 9.99e309, + 9.99e310, + 9.99e320, + 9.99e350, + 123456789012345678901234567890, + 987654321098765432109876543210, + 111111111111111111111111111111, + 999999999999999999999999999999, + 555555555555555555555555555555, + 1e309, + 1e310, + 1e320, + 1e350, + 1e400, + 9.99e308, + 9.99e309, + 9.99e310, + 9.99e320, + 9.99e350, + 123456789012345678901234567890, + 987654321098765432109876543210, + 111111111111111111111111111111, + 999999999999999999999999999999, + 555555555555555555555555555555, + 1e309, + 1e310, + 1e320, + 1e350, + 1e400, + 9.99e308, + 9.99e309, + 9.99e310, + 9.99e320, + 9.99e350, + 123456789012345678901234567890, + 987654321098765432109876543210, + 111111111111111111111111111111, + 999999999999999999999999999999, + 555555555555555555555555555555, + 1e309, + 1e310, + 1e320, + 1e350, + 1e400, + 9.99e308, + 9.99e309, + 9.99e310, + 9.99e320, + 9.99e350, + 123456789012345678901234567890, + 987654321098765432109876543210, + 111111111111111111111111111111, + 999999999999999999999999999999, + 555555555555555555555555555555, + 1e309, + 1e310, + 1e320, + 1e350, + 1e400, + 9.99e308, + 9.99e309, + 9.99e310, + 9.99e320, + 9.99e350, + 123456789012345678901234567890, + 987654321098765432109876543210, + 111111111111111111111111111111, + 999999999999999999999999999999, + 555555555555555555555555555555, + 1e309, + 1e310, + 1e320, + 1e350, + 1e400, + 9.99e308, + 9.99e309, + 9.99e310, + 9.99e320, + 9.99e350, + 123456789012345678901234567890, + 987654321098765432109876543210, + 111111111111111111111111111111, + 999999999999999999999999999999, + 555555555555555555555555555555, + 1e309, + 1e310, + 1e320, + 1e350, + 1e400 + ])JSON"}; + + assert( + std::ranges::all_of(sourcemeta::core::parse_json(document).as_array(), + [](const auto &item) { return item.is_decimal(); })); + + for (auto _ : state) { + auto result{sourcemeta::core::parse_json(document)}; + assert(result.is_array()); + benchmark::DoNotOptimize(result); + } +} + static void JSON_Fast_Hash_Helm_Chart_Lock(benchmark::State &state) { // From `helm-chart-lock` const auto document{sourcemeta::core::parse_json(R"JSON({ @@ -427,6 +542,7 @@ static void JSON_Object_Defines_Miss_Too_Large(benchmark::State &state) { BENCHMARK(JSON_Array_Of_Objects_Unique); BENCHMARK(JSON_Parse_1); BENCHMARK(JSON_Parse_Real); +BENCHMARK(JSON_Parse_Decimal); BENCHMARK(JSON_Fast_Hash_Helm_Chart_Lock); BENCHMARK(JSON_Equality_Helm_Chart_Lock); BENCHMARK(JSON_String_Equal)->Args({10})->Args({100}); diff --git a/config.cmake.in b/config.cmake.in index c628df9bf..ebfb83917 100644 --- a/config.cmake.in +++ b/config.cmake.in @@ -50,41 +50,63 @@ foreach(component ${SOURCEMETA_CORE_COMPONENTS}) elseif(component STREQUAL "uri") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake") elseif(component STREQUAL "json") + find_dependency(mpdecimal CONFIG) + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake") elseif(component STREQUAL "jsonl") + find_dependency(mpdecimal CONFIG) + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonl.cmake") elseif(component STREQUAL "jsonpointer") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_regex.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake") + find_dependency(mpdecimal CONFIG) + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake") elseif(component STREQUAL "jsonschema") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake") + find_dependency(mpdecimal CONFIG) + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonschema.cmake") elseif(component STREQUAL "yaml") + find_dependency(mpdecimal CONFIG) + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake") find_dependency(yaml CONFIG) include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_yaml.cmake") elseif(component STREQUAL "alterschema") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake") + find_dependency(mpdecimal CONFIG) + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonschema.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_alterschema.cmake") elseif(component STREQUAL "editorschema") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake") + find_dependency(mpdecimal CONFIG) + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonschema.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_editorschema.cmake") elseif(component STREQUAL "schemaconfig") - include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake") + find_dependency(mpdecimal CONFIG) + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_schemaconfig.cmake") diff --git a/src/core/json/include/sourcemeta/core/json_auto.h b/src/core/json/include/sourcemeta/core/json_auto.h index a2a6f599a..39896a4ca 100644 --- a/src/core/json/include/sourcemeta/core/json_auto.h +++ b/src/core/json/include/sourcemeta/core/json_auto.h @@ -161,6 +161,17 @@ auto from_json(const JSON &value) -> std::optional { } } +/// @ingroup json +template + requires std::is_same_v +auto from_json(const JSON &value) -> std::optional { + if (value.is_decimal()) { + return value.to_decimal(); + } else { + return std::nullopt; + } +} + // TODO: How can we keep this in the hash header that does not yet know about // JSON? /// @ingroup json diff --git a/src/core/json/include/sourcemeta/core/json_error.h b/src/core/json/include/sourcemeta/core/json_error.h index 53ec4b732..d1d0fa4f2 100644 --- a/src/core/json/include/sourcemeta/core/json_error.h +++ b/src/core/json/include/sourcemeta/core/json_error.h @@ -51,20 +51,6 @@ class SOURCEMETA_CORE_JSON_EXPORT JSONParseError : public std::exception { std::string message_{"Failed to parse the JSON document"}; }; -/// @ingroup json -/// This class represents a numeric integer limit parsing error -class SOURCEMETA_CORE_JSON_EXPORT JSONParseIntegerLimitError - : public JSONParseError { -public: - /// Create a parsing error - JSONParseIntegerLimitError(const std::uint64_t line, - const std::uint64_t column) - : JSONParseError{ - line, column, - "The JSON value is not representable by the IETF RFC 8259 " - "interoperable signed integer range"} {} -}; - /// @ingroup json /// This class represents a parsing error occurring from parsing a file class SOURCEMETA_CORE_JSON_EXPORT JSONFileParseError : public JSONParseError { diff --git a/src/core/json/include/sourcemeta/core/json_value.h b/src/core/json/include/sourcemeta/core/json_value.h index 58c81ed24..1d7d6a067 100644 --- a/src/core/json/include/sourcemeta/core/json_value.h +++ b/src/core/json/include/sourcemeta/core/json_value.h @@ -9,6 +9,8 @@ #include #include +#include + #include // std::any_of #include // assert #include // std::size_t @@ -55,7 +57,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { Real = 3, String = 4, Array = 5, - Object = 6 + Object = 6, + Decimal = 7 }; /// An optional callback that can be passed to parsing functions to obtain @@ -214,6 +217,12 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// A copy constructor for the object type. explicit JSON(const Object &value); + /// A copy constructor for the decimal type. + explicit JSON(const Decimal &value); + + /// A move constructor for the decimal type. + explicit JSON(Decimal &&value); + /// Misc constructors JSON(const JSON &); JSON(JSON &&) noexcept; @@ -463,6 +472,19 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// ``` [[nodiscard]] auto is_object() const noexcept -> bool; + /// Check if the input JSON document is an arbitrary precision decimal value. + /// For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::core::Decimal value{1234567890}; + /// const sourcemeta::core::JSON document{value}; + /// assert(document.is_decimal()); + /// ``` + [[nodiscard]] auto is_decimal() const noexcept -> bool; + /// Get the type of the JSON document. For example: /// /// ```cpp @@ -519,6 +541,20 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// ``` [[nodiscard]] auto to_real() const noexcept -> Real; + /// Convert a JSON instance into a decimal value. The result of this method + /// is undefined unless the JSON instance holds a decimal value. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::core::Decimal value{1234567890}; + /// const sourcemeta::core::JSON document{value}; + /// assert(document.is_decimal()); + /// assert(document.to_decimal().to_int64() == 1234567890); + /// ``` + [[nodiscard]] auto to_decimal() const noexcept -> const Decimal &; + /// Convert a JSON instance into a standard string value. The result of this /// method is undefined unless the JSON instance holds a string value. For /// example: @@ -1713,6 +1749,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { String data_string; Array data_array; Object data_object; + Decimal data_decimal; }; #if defined(_MSC_VER) #pragma warning(default : 4251) diff --git a/src/core/json/json.cc b/src/core/json/json.cc index cc668c541..53734fa08 100644 --- a/src/core/json/json.cc +++ b/src/core/json/json.cc @@ -50,9 +50,6 @@ auto read_json(const std::filesystem::path &path, auto stream{read_file(path)}; try { return parse_json(stream, callback); - } catch (const JSONParseIntegerLimitError &error) { - // For producing better error messages - throw JSONFileParseError(path, error); } catch (const JSONParseError &error) { // For producing better error messages throw JSONFileParseError(path, error); @@ -94,6 +91,8 @@ auto operator<<(std::basic_ostream &stream, return stream << "integer"; case sourcemeta::core::JSON::Type::Real: return stream << "real"; + case sourcemeta::core::JSON::Type::Decimal: + return stream << "decimal"; case sourcemeta::core::JSON::Type::String: return stream << "string"; case sourcemeta::core::JSON::Type::Array: diff --git a/src/core/json/json_value.cc b/src/core/json/json_value.cc index ecb9f8488..f1dea858b 100644 --- a/src/core/json/json_value.cc +++ b/src/core/json/json_value.cc @@ -92,6 +92,14 @@ JSON::JSON(const Object &value) : current_type{Type::Object} { new (&this->data_object) Object{value}; } +JSON::JSON(const Decimal &value) : current_type{Type::Decimal} { + new (&this->data_decimal) Decimal{value}; +} + +JSON::JSON(Decimal &&value) : current_type{Type::Decimal} { + new (&this->data_decimal) Decimal{std::move(value)}; +} + JSON::JSON(const JSON &other) : current_type{other.current_type} { switch (other.current_type) { case Type::Boolean: @@ -112,6 +120,9 @@ JSON::JSON(const JSON &other) : current_type{other.current_type} { case Type::Object: new (&this->data_object) Object{other.data_object}; break; + case Type::Decimal: + new (&this->data_decimal) Decimal{other.data_decimal}; + break; default: break; } @@ -140,6 +151,10 @@ JSON::JSON(JSON &&other) noexcept : current_type{other.current_type} { new (&this->data_object) Object{std::move(other.data_object)}; other.current_type = Type::Null; break; + case Type::Decimal: + new (&this->data_decimal) Decimal{std::move(other.data_decimal)}; + other.current_type = Type::Null; + break; default: break; } @@ -167,6 +182,9 @@ auto JSON::operator=(const JSON &other) -> JSON & { case Type::Object: new (&this->data_object) Object{other.data_object}; break; + case Type::Decimal: + new (&this->data_decimal) Decimal{other.data_decimal}; + break; default: break; } @@ -199,6 +217,10 @@ auto JSON::operator=(JSON &&other) noexcept -> JSON & { new (&this->data_object) Object{std::move(other.data_object)}; other.current_type = Type::Null; break; + case Type::Decimal: + new (&this->data_decimal) Decimal{std::move(other.data_decimal)}; + other.current_type = Type::Null; + break; default: break; } @@ -236,6 +258,19 @@ auto JSON::operator<(const JSON &other) const noexcept -> bool { return this->as_real() < other.as_real(); } + if ((this->type() == Type::Decimal && + (other.type() == Type::Integer || other.type() == Type::Real)) || + ((this->type() == Type::Integer || this->type() == Type::Real) && + other.type() == Type::Decimal)) { + const Decimal left = this->is_decimal() ? this->to_decimal() + : this->is_integer() ? Decimal{this->to_integer()} + : Decimal{this->to_real()}; + const Decimal right = other.is_decimal() ? other.to_decimal() + : other.is_integer() ? Decimal{other.to_integer()} + : Decimal{other.to_real()}; + return left < right; + } + if (this->type() != other.type()) { return this->current_type < other.current_type; } @@ -249,6 +284,8 @@ auto JSON::operator<(const JSON &other) const noexcept -> bool { return this->to_integer() < other.to_integer(); case Type::Real: return this->to_real() < other.to_real(); + case Type::Decimal: + return this->to_decimal() < other.to_decimal(); case Type::String: return this->to_string() < other.to_string(); case Type::Array: @@ -278,6 +315,19 @@ auto JSON::operator==(const JSON &other) const noexcept -> bool { return this->as_real() == other.as_real(); } + if ((this->type() == Type::Decimal && + (other.type() == Type::Integer || other.type() == Type::Real)) || + ((this->type() == Type::Integer || this->type() == Type::Real) && + other.type() == Type::Decimal)) { + const Decimal left = this->is_decimal() ? this->to_decimal() + : this->is_integer() ? Decimal{this->to_integer()} + : Decimal{this->to_real()}; + const Decimal right = other.is_decimal() ? other.to_decimal() + : other.is_integer() ? Decimal{other.to_integer()} + : Decimal{other.to_real()}; + return left == right; + } + if (this->current_type != other.current_type) { return false; } @@ -289,6 +339,8 @@ auto JSON::operator==(const JSON &other) const noexcept -> bool { return this->data_integer == other.data_integer; case Type::Real: return this->data_real == other.data_real; + case Type::Decimal: + return this->data_decimal == other.data_decimal; case Type::String: return this->data_string == other.data_string; case Type::Array: @@ -304,7 +356,15 @@ auto JSON::operator+(const JSON &other) const -> JSON { assert(this->is_number()); assert(other.is_number()); - if (this->is_integer() && other.is_integer()) { + if (this->is_decimal() || other.is_decimal()) { + const Decimal left = this->is_decimal() ? this->to_decimal() + : this->is_integer() ? Decimal{this->to_integer()} + : Decimal{this->to_real()}; + const Decimal right = other.is_decimal() ? other.to_decimal() + : other.is_integer() ? Decimal{other.to_integer()} + : Decimal{other.to_real()}; + return JSON{left + right}; + } else if (this->is_integer() && other.is_integer()) { return JSON{this->to_integer() + other.to_integer()}; } else if (this->is_integer() && other.is_real()) { return JSON{this->as_real() + other.to_real()}; @@ -319,7 +379,15 @@ auto JSON::operator-(const JSON &other) const -> JSON { assert(this->is_number()); assert(other.is_number()); - if (this->is_integer() && other.is_integer()) { + if (this->is_decimal() || other.is_decimal()) { + const Decimal left = this->is_decimal() ? this->to_decimal() + : this->is_integer() ? Decimal{this->to_integer()} + : Decimal{this->to_real()}; + const Decimal right = other.is_decimal() ? other.to_decimal() + : other.is_integer() ? Decimal{other.to_integer()} + : Decimal{other.to_real()}; + return JSON{left - right}; + } else if (this->is_integer() && other.is_integer()) { return JSON{this->to_integer() - other.to_integer()}; } else if (this->is_integer() && other.is_real()) { return JSON{this->as_real() - other.to_real()}; @@ -366,7 +434,7 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { } [[nodiscard]] auto JSON::is_number() const noexcept -> bool { - return this->is_integer() || this->is_real(); + return this->is_integer() || this->is_real() || this->is_decimal(); } [[nodiscard]] auto JSON::is_positive() const noexcept -> bool { @@ -375,6 +443,8 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { return this->to_integer() >= 0; case Type::Real: return this->to_real() >= static_cast(0.0); + case Type::Decimal: + return this->to_decimal() >= Decimal{0}; default: return false; } @@ -392,6 +462,10 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { return this->current_type == Type::Object; } +[[nodiscard]] auto JSON::is_decimal() const noexcept -> bool { + return this->current_type == Type::Decimal; +} + [[nodiscard]] auto JSON::type() const noexcept -> Type { return this->current_type; } @@ -411,6 +485,11 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { return this->data_real; } +[[nodiscard]] auto JSON::to_decimal() const noexcept -> const Decimal & { + assert(this->is_decimal()); + return this->data_decimal; +} + [[nodiscard]] auto JSON::to_string() const noexcept -> const JSON::String & { assert(this->is_string()); return this->data_string; @@ -927,6 +1006,9 @@ auto JSON::maybe_destruct_union() -> void { case Type::Object: this->data_object.~JSONObject(); break; + case Type::Decimal: + this->data_decimal.~Decimal(); + break; default: break; } diff --git a/src/core/json/parser.h b/src/core/json/parser.h index 491331d5b..82a7e197c 100644 --- a/src/core/json/parser.h +++ b/src/core/json/parser.h @@ -4,10 +4,13 @@ #include #include +#include + #include "grammar.h" #include // assert #include // std::isxdigit +#include // std::isinf, std::isnan #include // std::size_t #include // std::uint64_t #include // std::reference_wrapper @@ -291,21 +294,30 @@ auto parse_string( template auto parse_number_integer(const std::uint64_t line, const std::uint64_t column, const std::basic_string &string) - -> std::int64_t { + -> JSON { try { - return std::stoll(string); + return JSON{static_cast(std::stoll(string))}; } catch (const std::out_of_range &) { - throw JSONParseIntegerLimitError(line, column); + try { + return JSON{Decimal{string}}; + } catch (const DecimalParseError &) { + throw JSONParseError(line, column); + } } } template auto parse_number_real(const std::uint64_t line, const std::uint64_t column, - const std::basic_string &string) - -> double { + const std::basic_string &string) -> JSON { try { - return std::stod(string); + return JSON{std::stod(string)}; } catch (const std::out_of_range &) { + try { + return JSON{Decimal{string}}; + } catch (const DecimalParseError &) { + throw JSONParseError(line, column); + } + } catch (const std::invalid_argument &) { throw JSONParseError(line, column); } } @@ -316,7 +328,7 @@ auto parse_number_exponent_rest( std::basic_istream &stream, std::basic_ostringstream> - &result) -> double { + &result) -> JSON { while (!stream.eof()) { const typename JSON::Char character{ static_cast(stream.peek())}; @@ -349,7 +361,7 @@ auto parse_number_exponent( std::basic_istream &stream, std::basic_ostringstream> - &result) -> double { + &result) -> JSON { const typename JSON::Char character{ static_cast(stream.get())}; column += 1; @@ -378,7 +390,7 @@ auto parse_number_exponent_first( std::basic_istream &stream, std::basic_ostringstream> - &result) -> double { + &result) -> JSON { const typename JSON::Char character{ static_cast(stream.get())}; column += 1; @@ -417,7 +429,7 @@ auto parse_number_fractional( std::basic_istream &stream, std::basic_ostringstream> - &result) -> double { + &result) -> JSON { while (!stream.eof()) { const typename JSON::Char character{ static_cast(stream.peek())}; @@ -461,7 +473,7 @@ auto parse_number_fractional_first( std::basic_istream &stream, std::basic_ostringstream> - &result) -> double { + &result) -> JSON { const typename JSON::Char character{ static_cast(stream.peek())}; switch (character) { @@ -764,6 +776,10 @@ auto internal_parse_json( CALLBACK_PRE_WITH_POSITION(Integer, current_line, current_column, JSON{nullptr}); CALLBACK_POST(Integer, value); + } else if (value.is_decimal()) { + CALLBACK_PRE_WITH_POSITION(Decimal, current_line, current_column, + JSON{nullptr}); + CALLBACK_POST(Decimal, value); } else { CALLBACK_PRE_WITH_POSITION(Real, current_line, current_column, JSON{nullptr}); @@ -889,6 +905,9 @@ auto internal_parse_json( if (value.is_integer()) { CALLBACK_PRE_WITH_POSITION(Integer, current_line, current_column, JSON{frames.top().get().size()}); + } else if (value.is_decimal()) { + CALLBACK_PRE_WITH_POSITION(Decimal, current_line, current_column, + JSON{frames.top().get().size()}); } else { CALLBACK_PRE_WITH_POSITION(Real, current_line, current_column, JSON{frames.top().get().size()}); @@ -898,6 +917,8 @@ auto internal_parse_json( if (value.is_integer()) { CALLBACK_POST(Integer, frames.top().get().back()); + } else if (value.is_decimal()) { + CALLBACK_POST(Decimal, frames.top().get().back()); } else { CALLBACK_POST(Real, frames.top().get().back()); } @@ -1100,6 +1121,8 @@ auto internal_parse_json( internal::parse_number(line, column, stream, character)}; if (value.is_integer()) { CALLBACK_PRE_WITH_POSITION(Integer, key_line, key_column, JSON{key}); + } else if (value.is_decimal()) { + CALLBACK_PRE_WITH_POSITION(Decimal, key_line, key_column, JSON{key}); } else { CALLBACK_PRE_WITH_POSITION(Real, key_line, key_column, JSON{key}); } @@ -1108,6 +1131,8 @@ auto internal_parse_json( if (value.is_integer()) { CALLBACK_POST(Integer, frames.top().get().at(key)); + } else if (value.is_decimal()) { + CALLBACK_POST(Decimal, frames.top().get().at(key)); } else { CALLBACK_POST(Real, frames.top().get().at(key)); } diff --git a/src/core/json/stringify.h b/src/core/json/stringify.h index 6283914be..78e9d77c0 100644 --- a/src/core/json/stringify.h +++ b/src/core/json/stringify.h @@ -552,6 +552,9 @@ auto stringify( case JSON::Type::Object: stringify(document.as_object(), stream); break; + case JSON::Type::Decimal: + stream << document.to_decimal().to_string(); + break; } } @@ -587,6 +590,9 @@ auto prettify( case JSON::Type::Object: prettify(document.as_object(), stream, indentation, indent_by); break; + case JSON::Type::Decimal: + stream << document.to_decimal().to_string(); + break; } } diff --git a/src/core/yaml/yaml.cc b/src/core/yaml/yaml.cc index cb71646fa..b5b35f58e 100644 --- a/src/core/yaml/yaml.cc +++ b/src/core/yaml/yaml.cc @@ -109,6 +109,8 @@ auto consume_scalar_event(yaml_event_t *event, type = sourcemeta::core::JSON::Type::Integer; } else if (result.is_real()) { type = sourcemeta::core::JSON::Type::Real; + } else if (result.is_decimal()) { + type = sourcemeta::core::JSON::Type::Decimal; } else if (result.is_string()) { type = sourcemeta::core::JSON::Type::String; } diff --git a/src/extension/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h b/src/extension/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h index bf0f3d190..c9bf47dc1 100644 --- a/src/extension/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h +++ b/src/extension/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h @@ -35,6 +35,25 @@ class ExclusiveMaximumIntegerToMaximum final : public SchemaTransformRule { auto new_maximum = schema.at("exclusiveMaximum"); new_maximum += sourcemeta::core::JSON{-1}; schema.at("exclusiveMaximum").into(new_maximum); + schema.rename("exclusiveMaximum", "maximum"); + } else if (schema.at("exclusiveMaximum").is_decimal()) { + const auto current{schema.at("exclusiveMaximum").to_decimal()}; + auto new_value{current.to_integral()}; + if (new_value > current) { + new_value -= sourcemeta::core::Decimal{1}; + } + + if (current.is_integer()) { + new_value -= sourcemeta::core::Decimal{1}; + } + + if (new_value.is_int64()) { + schema.at("exclusiveMaximum") + .into(sourcemeta::core::JSON{new_value.to_int64()}); + } else { + schema.at("exclusiveMaximum").into(sourcemeta::core::JSON{new_value}); + } + schema.rename("exclusiveMaximum", "maximum"); } else { const auto current{schema.at("exclusiveMaximum").to_real()}; diff --git a/src/extension/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h b/src/extension/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h index c7ca45237..733d3e670 100644 --- a/src/extension/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h +++ b/src/extension/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h @@ -35,6 +35,25 @@ class ExclusiveMinimumIntegerToMinimum final : public SchemaTransformRule { auto new_minimum = schema.at("exclusiveMinimum"); new_minimum += sourcemeta::core::JSON{1}; schema.at("exclusiveMinimum").into(new_minimum); + schema.rename("exclusiveMinimum", "minimum"); + } else if (schema.at("exclusiveMinimum").is_decimal()) { + const auto current{schema.at("exclusiveMinimum").to_decimal()}; + auto new_value{current.to_integral()}; + if (new_value < current) { + new_value += sourcemeta::core::Decimal{1}; + } + + if (current.is_integer()) { + new_value += sourcemeta::core::Decimal{1}; + } + + if (new_value.is_int64()) { + schema.at("exclusiveMinimum") + .into(sourcemeta::core::JSON{new_value.to_int64()}); + } else { + schema.at("exclusiveMinimum").into(sourcemeta::core::JSON{new_value}); + } + schema.rename("exclusiveMinimum", "minimum"); } else { const auto current{schema.at("exclusiveMinimum").to_real()}; diff --git a/src/extension/alterschema/linter/maximum_real_for_integer.h b/src/extension/alterschema/linter/maximum_real_for_integer.h index ac13b2d7f..ccdefe29c 100644 --- a/src/extension/alterschema/linter/maximum_real_for_integer.h +++ b/src/extension/alterschema/linter/maximum_real_for_integer.h @@ -28,13 +28,30 @@ class MaximumRealForInteger final : public SchemaTransformRule { schema.is_object() && schema.defines("type") && schema.at("type").is_string() && schema.at("type").to_string() == "integer" && - schema.defines("maximum") && schema.at("maximum").is_real()); + schema.defines("maximum") && + (schema.at("maximum").is_real() || + (schema.at("maximum").is_decimal() && + !schema.at("maximum").to_decimal().is_integer()))); return APPLIES_TO_KEYWORDS("maximum"); } auto transform(JSON &schema, const Result &) const -> void override { - const auto current{schema.at("maximum").to_real()}; - const auto new_value{static_cast(std::floor(current))}; - schema.assign("maximum", sourcemeta::core::JSON{new_value}); + if (schema.at("maximum").is_decimal()) { + auto current{schema.at("maximum").to_decimal()}; + auto new_value{current.to_integral()}; + if (new_value > current) { + new_value -= sourcemeta::core::Decimal{1}; + } + + if (new_value.is_int64()) { + schema.assign("maximum", sourcemeta::core::JSON{new_value.to_int64()}); + } else { + schema.assign("maximum", sourcemeta::core::JSON{std::move(new_value)}); + } + } else { + const auto current{schema.at("maximum").to_real()}; + const auto new_value{static_cast(std::floor(current))}; + schema.assign("maximum", sourcemeta::core::JSON{new_value}); + } } }; diff --git a/src/extension/alterschema/linter/minimum_real_for_integer.h b/src/extension/alterschema/linter/minimum_real_for_integer.h index dcf5c752b..5cb69f0c0 100644 --- a/src/extension/alterschema/linter/minimum_real_for_integer.h +++ b/src/extension/alterschema/linter/minimum_real_for_integer.h @@ -28,13 +28,30 @@ class MinimumRealForInteger final : public SchemaTransformRule { schema.is_object() && schema.defines("type") && schema.at("type").is_string() && schema.at("type").to_string() == "integer" && - schema.defines("minimum") && schema.at("minimum").is_real()); + schema.defines("minimum") && + (schema.at("minimum").is_real() || + (schema.at("minimum").is_decimal() && + !schema.at("minimum").to_decimal().is_integer()))); return APPLIES_TO_KEYWORDS("minimum"); } auto transform(JSON &schema, const Result &) const -> void override { - const auto current{schema.at("minimum").to_real()}; - const auto new_value{static_cast(std::ceil(current))}; - schema.assign("minimum", sourcemeta::core::JSON{new_value}); + if (schema.at("minimum").is_decimal()) { + const auto current{schema.at("minimum").to_decimal()}; + auto new_value{current.to_integral()}; + if (new_value < current) { + new_value += sourcemeta::core::Decimal{1}; + } + + if (new_value.is_int64()) { + schema.assign("minimum", sourcemeta::core::JSON{new_value.to_int64()}); + } else { + schema.assign("minimum", sourcemeta::core::JSON{new_value}); + } + } else { + const auto current{schema.at("minimum").to_real()}; + const auto new_value{static_cast(std::ceil(current))}; + schema.assign("minimum", sourcemeta::core::JSON{new_value}); + } } }; diff --git a/src/extension/alterschema/linter/multiple_of_default.h b/src/extension/alterschema/linter/multiple_of_default.h index 8a6fb2299..fa986e0da 100644 --- a/src/extension/alterschema/linter/multiple_of_default.h +++ b/src/extension/alterschema/linter/multiple_of_default.h @@ -25,7 +25,10 @@ class MultipleOfDefault final : public SchemaTransformRule { ((schema.at("multipleOf").is_integer() && schema.at("multipleOf").to_integer() == 1) || (schema.at("multipleOf").is_real() && - schema.at("multipleOf").to_real() == 1.0))); + schema.at("multipleOf").to_real() == 1.0) || + (schema.at("multipleOf").is_decimal() && + schema.at("multipleOf").to_decimal() == + sourcemeta::core::Decimal{1}))); return APPLIES_TO_KEYWORDS("multipleOf"); } diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index 6f7cc98eb..68b408c9a 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -1956,6 +1956,62 @@ TEST(AlterSchema_lint_2019_09, equal_numeric_bounds_to_const_2) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_2019_09, exclusive_maximum_integer_to_maximum_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer", + "exclusiveMaximum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_STATIC_ANALYSIS(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer", + "multipleOf": 1, + "maximum": 10.00000000000000e+399 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, exclusive_minimum_integer_to_minimum_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer", + "exclusiveMinimum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_STATIC_ANALYSIS(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer", + "multipleOf": 1, + "minimum": 1.0e+400 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, equal_numeric_bounds_to_const_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer", + "minimum": 1.0e400, + "maximum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": 1.0e+400 + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_2019_09, unnecessary_allof_wrapper_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", diff --git a/test/alterschema/alterschema_lint_2020_12_test.cc b/test/alterschema/alterschema_lint_2020_12_test.cc index b6dbef0b0..f8e7283af 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -1915,6 +1915,62 @@ TEST(AlterSchema_lint_2020_12, equal_numeric_bounds_to_const_2) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_2020_12, exclusive_maximum_integer_to_maximum_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", + "exclusiveMaximum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_STATIC_ANALYSIS(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", + "multipleOf": 1, + "maximum": 10.00000000000000e+399 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, exclusive_minimum_integer_to_minimum_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", + "exclusiveMinimum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_STATIC_ANALYSIS(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", + "multipleOf": 1, + "minimum": 1.0e+400 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, equal_numeric_bounds_to_const_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", + "minimum": 1.0e400, + "maximum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 1.0e+400 + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_2020_12, unnecessary_allof_wrapper_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", diff --git a/test/alterschema/alterschema_lint_draft1_test.cc b/test/alterschema/alterschema_lint_draft1_test.cc index dda2b9298..5c74757e5 100644 --- a/test/alterschema/alterschema_lint_draft1_test.cc +++ b/test/alterschema/alterschema_lint_draft1_test.cc @@ -464,6 +464,42 @@ TEST(AlterSchema_lint_draft1, minimum_real_for_integer_1) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft1, maximum_real_for_integer_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-01/schema#", + "type": "integer", + "maximum": 1.23456789012345678901234567890e500 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-01/schema#", + "type": "integer", + "maximum": 1.23456789012345678901234567890e+500 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft1, minimum_real_for_integer_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-01/schema#", + "type": "integer", + "minimum": 9.99999999999999999999999999999e400 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-01/schema#", + "type": "integer", + "minimum": 99.9999999999999999999999999999e+399 + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_draft1, properties_default_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-01/schema#", diff --git a/test/alterschema/alterschema_lint_draft2_test.cc b/test/alterschema/alterschema_lint_draft2_test.cc index 3d7b63590..431cd19ed 100644 --- a/test/alterschema/alterschema_lint_draft2_test.cc +++ b/test/alterschema/alterschema_lint_draft2_test.cc @@ -464,6 +464,42 @@ TEST(AlterSchema_lint_draft2, minimum_real_for_integer_1) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft2, maximum_real_for_integer_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-02/schema#", + "type": "integer", + "maximum": 1.23456789012345678901234567890e500 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-02/schema#", + "type": "integer", + "maximum": 1.23456789012345678901234567890e+500 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft2, minimum_real_for_integer_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-02/schema#", + "type": "integer", + "minimum": 9.99999999999999999999999999999e400 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-02/schema#", + "type": "integer", + "minimum": 99.9999999999999999999999999999e+399 + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_draft2, properties_default_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-02/schema#", diff --git a/test/alterschema/alterschema_lint_draft3_test.cc b/test/alterschema/alterschema_lint_draft3_test.cc index 27d455e5a..b25b15473 100644 --- a/test/alterschema/alterschema_lint_draft3_test.cc +++ b/test/alterschema/alterschema_lint_draft3_test.cc @@ -464,6 +464,42 @@ TEST(AlterSchema_lint_draft3, minimum_real_for_integer_1) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft3, maximum_real_for_integer_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "type": "integer", + "maximum": 1.23456789012345678901234567890e500 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "type": "integer", + "maximum": 1.23456789012345678901234567890e+500 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft3, minimum_real_for_integer_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "type": "integer", + "minimum": 9.99999999999999999999999999999e400 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "type": "integer", + "minimum": 99.9999999999999999999999999999e+399 + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_draft3, dependent_required_tautology_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-03/schema#", diff --git a/test/alterschema/alterschema_lint_draft4_test.cc b/test/alterschema/alterschema_lint_draft4_test.cc index a59d6074b..0b9bbdf5c 100644 --- a/test/alterschema/alterschema_lint_draft4_test.cc +++ b/test/alterschema/alterschema_lint_draft4_test.cc @@ -572,6 +572,78 @@ TEST(AlterSchema_lint_draft4, minimum_real_for_integer_1) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft4, maximum_real_for_integer_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "integer", + "maximum": 1.23456789012345678901234567890e500 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "integer", + "maximum": 1.23456789012345678901234567890e+500 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft4, maximum_real_for_integer_decimal_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "integer", + "maximum": 9.99999999999999999999999999999e400 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "integer", + "maximum": 9.99999999999999999999999999999e+400 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft4, minimum_real_for_integer_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "integer", + "minimum": 1.23456789012345678901234567890e500 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "integer", + "minimum": 1.23456789012345678901234567890e+500 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft4, minimum_real_for_integer_decimal_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "integer", + "minimum": 9.99999999999999999999999999999e400 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "integer", + "minimum": 99.9999999999999999999999999999e+399 + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_draft4, dependent_required_tautology_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-04/schema#", @@ -1070,6 +1142,42 @@ TEST(AlterSchema_lint_draft4, equal_numeric_bounds_to_enum_2) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft4, equal_numeric_bounds_to_enum_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "integer", + "minimum": 999999999999999999999999999999, + "maximum": 999999999999999999999999999999 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "enum": [ 999999999999999999999999999999 ] + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft4, equal_numeric_bounds_to_enum_decimal_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "number", + "minimum": 1.23456789012345678901234567890e309, + "maximum": 1.23456789012345678901234567890e309 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "enum": [ 1.23456789012345678901234567890e309 ] + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_draft4, unnecessary_allof_wrapper_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-04/schema#", diff --git a/test/alterschema/alterschema_lint_draft6_test.cc b/test/alterschema/alterschema_lint_draft6_test.cc index bf992c0e4..24ad905b4 100644 --- a/test/alterschema/alterschema_lint_draft6_test.cc +++ b/test/alterschema/alterschema_lint_draft6_test.cc @@ -1473,6 +1473,62 @@ TEST(AlterSchema_lint_draft6, equal_numeric_bounds_to_const_2) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft6, exclusive_maximum_integer_to_maximum_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "integer", + "exclusiveMaximum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_STATIC_ANALYSIS(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "integer", + "multipleOf": 1, + "maximum": 10.00000000000000e+399 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft6, exclusive_minimum_integer_to_minimum_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "integer", + "exclusiveMinimum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_STATIC_ANALYSIS(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "integer", + "multipleOf": 1, + "minimum": 1.0e+400 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft6, equal_numeric_bounds_to_const_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "integer", + "minimum": 1.0e400, + "maximum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "const": 1.0e+400 + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_draft6, unnecessary_allof_wrapper_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index c5719fb8f..862aace83 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -858,6 +858,42 @@ TEST(AlterSchema_lint_draft7, minimum_real_for_integer_1) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft7, maximum_real_for_integer_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "maximum": 9.99999999999999999999999999999e400 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "maximum": 9.99999999999999999999999999999e+400 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, minimum_real_for_integer_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "minimum": 9.99999999999999999999999999999e400 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "minimum": 99.9999999999999999999999999999e+399 + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_draft7, dependent_required_tautology_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", @@ -1451,6 +1487,82 @@ TEST(AlterSchema_lint_draft7, exclusive_minimum_integer_to_minimum_5) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft7, exclusive_maximum_integer_to_maximum_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "exclusiveMaximum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_STATIC_ANALYSIS(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "multipleOf": 1, + "maximum": 10.00000000000000e+399 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, exclusive_maximum_integer_to_maximum_decimal_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "exclusiveMaximum": 9.99999999999999999999999999999e400 + })JSON"); + + LINT_AND_FIX_FOR_STATIC_ANALYSIS(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "multipleOf": 1, + "maximum": 100.0000000000000e+399 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, exclusive_minimum_integer_to_minimum_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "exclusiveMinimum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_STATIC_ANALYSIS(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "multipleOf": 1, + "minimum": 1.0e+400 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, exclusive_minimum_integer_to_minimum_decimal_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "exclusiveMinimum": 9.99999999999999999999999999999e400 + })JSON"); + + LINT_AND_FIX_FOR_STATIC_ANALYSIS(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "multipleOf": 1, + "minimum": 1.0e+401 + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_draft7, boolean_true_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", @@ -1572,6 +1684,42 @@ TEST(AlterSchema_lint_draft7, equal_numeric_bounds_to_const_2) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft7, equal_numeric_bounds_to_const_decimal_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "minimum": 1.0e400, + "maximum": 1.0e400 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "const": 1.0e+400 + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_draft7, equal_numeric_bounds_to_const_decimal_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "number", + "minimum": 1.23456789012345678901234567890e500, + "maximum": 1.23456789012345678901234567890e500 + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "const": 1.23456789012345678901234567890e+500 + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_lint_draft7, unnecessary_allof_wrapper_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/test/json/CMakeLists.txt b/test/json/CMakeLists.txt index 2e633e06e..f933b7fa2 100644 --- a/test/json/CMakeLists.txt +++ b/test/json/CMakeLists.txt @@ -7,10 +7,12 @@ sourcemeta_googletest(NAMESPACE sourcemeta PROJECT core NAME json json_integer_test.cc json_null_test.cc json_number_test.cc + json_decimal_test.cc json_object_test.cc json_parse_callback_test.cc json_parse_error_test.cc json_parse_test.cc + json_parse_roundtrip_test.cc json_prettify_test.cc json_real_test.cc json_string_test.cc diff --git a/test/json/json_auto_test.cc b/test/json/json_auto_test.cc index c0e27c727..0b273a8f3 100644 --- a/test/json/json_auto_test.cc +++ b/test/json/json_auto_test.cc @@ -824,3 +824,94 @@ TEST(JSON_auto, tuple_with_pair) { EXPECT_TRUE(back.has_value()); EXPECT_EQ(value, back.value()); } + +TEST(JSON_auto, decimal) { + const sourcemeta::core::Decimal value{"123456789012345678901234567890"}; + const auto result{sourcemeta::core::to_json(value)}; + const sourcemeta::core::JSON expected{ + sourcemeta::core::Decimal{"123456789012345678901234567890"}}; + EXPECT_EQ(result, expected); + EXPECT_TRUE(result.is_decimal()); + const auto back{ + sourcemeta::core::from_json(result)}; + EXPECT_TRUE(back.has_value()); + EXPECT_EQ(value, back.value()); +} + +TEST(JSON_auto, optional_decimal_with_value) { + const std::optional value{ + sourcemeta::core::Decimal{"987654321098765432109876543210"}}; + const auto result{sourcemeta::core::to_json(value)}; + const sourcemeta::core::JSON expected{ + sourcemeta::core::Decimal{"987654321098765432109876543210"}}; + EXPECT_EQ(result, expected); + EXPECT_TRUE(result.is_decimal()); + const auto back{ + sourcemeta::core::from_json>( + result)}; + EXPECT_TRUE(back.has_value()); + EXPECT_EQ(value, back.value()); +} + +TEST(JSON_auto, optional_decimal_without_value) { + const std::optional value; + const auto result{sourcemeta::core::to_json(value)}; + const auto expected{sourcemeta::core::parse_json(R"JSON(null)JSON")}; + EXPECT_EQ(result, expected); + const auto back{ + sourcemeta::core::from_json>( + result)}; + EXPECT_TRUE(back.has_value()); + EXPECT_FALSE(back.value().has_value()); +} + +TEST(JSON_auto, vector_of_decimals) { + const std::vector value{ + sourcemeta::core::Decimal{"123456789012345678901234567890"}}; + const auto result{sourcemeta::core::to_json(value)}; + + const auto expected{sourcemeta::core::parse_json(R"JSON([ + 123456789012345678901234567890 + ])JSON")}; + + EXPECT_EQ(result, expected); + const auto back{ + sourcemeta::core::from_json>( + result)}; + EXPECT_TRUE(back.has_value()); + EXPECT_EQ(value, back.value()); +} + +TEST(JSON_auto, map_of_decimals) { + const std::map value{ + {"large", sourcemeta::core::Decimal{"999999999999999999999999999999"}}}; + const auto result{sourcemeta::core::to_json(value)}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "large": 999999999999999999999999999999 + })JSON")}; + + EXPECT_EQ(result, expected); + const auto back{sourcemeta::core::from_json< + std::map>(result)}; + EXPECT_TRUE(back.has_value()); + EXPECT_EQ(value, back.value()); +} + +TEST(JSON_auto, vector_of_optional_decimals) { + const std::vector> value{ + sourcemeta::core::Decimal{"123456789012345678901234567890"}, + std::nullopt}; + const auto result{sourcemeta::core::to_json(value)}; + + const auto expected{sourcemeta::core::parse_json(R"JSON([ + 123456789012345678901234567890, + null + ])JSON")}; + + EXPECT_EQ(result, expected); + const auto back{sourcemeta::core::from_json< + std::vector>>(result)}; + EXPECT_TRUE(back.has_value()); + EXPECT_EQ(value, back.value()); +} diff --git a/test/json/json_decimal_test.cc b/test/json/json_decimal_test.cc new file mode 100644 index 000000000..0ffc3f229 --- /dev/null +++ b/test/json/json_decimal_test.cc @@ -0,0 +1,474 @@ +#include + +#include + +TEST(JSON_decimal, positive) { + const sourcemeta::core::Decimal value{42}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_int64(), 42); +} + +TEST(JSON_decimal, type) { + const sourcemeta::core::Decimal value{5}; + const sourcemeta::core::JSON document{value}; + EXPECT_EQ(document.type(), sourcemeta::core::JSON::Type::Decimal); +} + +TEST(JSON_decimal, negative) { + const sourcemeta::core::Decimal value{-10}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_int64(), -10); +} + +TEST(JSON_decimal, zero) { + const sourcemeta::core::Decimal value{0}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_int64(), 0); +} + +TEST(JSON_decimal, copy_constructor_from_json) { + const sourcemeta::core::Decimal value{123}; + const sourcemeta::core::JSON decimal{value}; + const sourcemeta::core::JSON document{decimal}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_int64(), 123); +} + +TEST(JSON_decimal, copy_constructor_from_decimal) { + const sourcemeta::core::Decimal value{789}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_int64(), 789); +} + +TEST(JSON_decimal, move_constructor) { + sourcemeta::core::Decimal value{456}; + const sourcemeta::core::JSON document{std::move(value)}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_int64(), 456); +} + +TEST(JSON_decimal, copy_assignment) { + const sourcemeta::core::Decimal value{999}; + const sourcemeta::core::JSON source{value}; + sourcemeta::core::JSON document{5}; + document = source; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_int64(), 999); +} + +TEST(JSON_decimal, move_assignment) { + sourcemeta::core::Decimal value{777}; + sourcemeta::core::JSON source{value}; + sourcemeta::core::JSON document{10}; + document = std::move(source); + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_int64(), 777); +} + +TEST(JSON_decimal, large_positive) { + const sourcemeta::core::Decimal value{"123456789012345678901234567890"}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_string(), + "123456789012345678901234567890"); +} + +TEST(JSON_decimal, large_negative) { + const sourcemeta::core::Decimal value{"-987654321098765432109876543210"}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_string(), + "-987654321098765432109876543210"); +} + +TEST(JSON_decimal, fractional) { + const sourcemeta::core::Decimal value{"3.14159265358979323846"}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_string(), "3.14159265358979323846"); +} + +TEST(JSON_decimal, negative_fractional) { + const sourcemeta::core::Decimal value{"-2.71828182845904523536"}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_string(), "-2.71828182845904523536"); +} + +TEST(JSON_decimal, is_not_integer) { + const sourcemeta::core::Decimal value{42}; + const sourcemeta::core::JSON document{value}; + EXPECT_FALSE(document.is_integer()); +} + +TEST(JSON_decimal, is_not_real) { + const sourcemeta::core::Decimal value{"3.14"}; + const sourcemeta::core::JSON document{value}; + EXPECT_FALSE(document.is_real()); +} + +TEST(JSON_decimal, is_not_string) { + const sourcemeta::core::Decimal value{100}; + const sourcemeta::core::JSON document{value}; + EXPECT_FALSE(document.is_string()); +} + +TEST(JSON_decimal, is_not_boolean) { + const sourcemeta::core::Decimal value{1}; + const sourcemeta::core::JSON document{value}; + EXPECT_FALSE(document.is_boolean()); +} + +TEST(JSON_decimal, is_not_null) { + const sourcemeta::core::Decimal value{0}; + const sourcemeta::core::JSON document{value}; + EXPECT_FALSE(document.is_null()); +} + +TEST(JSON_decimal, is_not_array) { + const sourcemeta::core::Decimal value{42}; + const sourcemeta::core::JSON document{value}; + EXPECT_FALSE(document.is_array()); +} + +TEST(JSON_decimal, is_not_object) { + const sourcemeta::core::Decimal value{42}; + const sourcemeta::core::JSON document{value}; + EXPECT_FALSE(document.is_object()); +} + +TEST(JSON_decimal, to_decimal_returns_reference) { + const sourcemeta::core::Decimal value{555}; + const sourcemeta::core::JSON document{value}; + const sourcemeta::core::Decimal &reference{document.to_decimal()}; + EXPECT_EQ(reference.to_int64(), 555); + EXPECT_EQ(&reference, &document.to_decimal()); +} + +TEST(JSON_decimal, copy_preserves_precision) { + const sourcemeta::core::Decimal value{ + "12345678901234567890.123456789012345678901234567890"}; + const sourcemeta::core::JSON original{value}; + const sourcemeta::core::JSON copy{original}; + EXPECT_EQ(copy.to_decimal().to_string(), original.to_decimal().to_string()); +} + +TEST(JSON_decimal, assignment_from_other_type) { + const sourcemeta::core::Decimal value{888}; + const sourcemeta::core::JSON decimal{value}; + sourcemeta::core::JSON document{true}; + EXPECT_TRUE(document.is_boolean()); + document = decimal; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_int64(), 888); +} + +TEST(JSON_decimal, assignment_to_other_type) { + const sourcemeta::core::Decimal value{333}; + sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + document = sourcemeta::core::JSON{"hello"}; + EXPECT_TRUE(document.is_string()); + EXPECT_FALSE(document.is_decimal()); +} + +TEST(JSON_decimal, very_small_fractional) { + const sourcemeta::core::Decimal value{"0.000000000000000000000000000001"}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_string(), "1e-30"); +} + +TEST(JSON_decimal, scientific_notation) { + const sourcemeta::core::Decimal value{"1.23e10"}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_string(), "12.3e+9"); +} + +TEST(JSON_decimal, negative_scientific_notation) { + const sourcemeta::core::Decimal value{"-4.56e-5"}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_string(), "-0.0000456"); +} + +TEST(JSON_decimal, unsigned_integer_construction) { + const sourcemeta::core::Decimal value{ + static_cast(18446744073709551615ULL)}; + const sourcemeta::core::JSON document{value}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_uint64(), 18446744073709550000ULL); +} + +TEST(JSON_decimal, multiple_copies) { + const sourcemeta::core::Decimal value{111}; + const sourcemeta::core::JSON first{value}; + const sourcemeta::core::JSON second{first}; + const sourcemeta::core::JSON third{second}; + EXPECT_TRUE(third.is_decimal()); + EXPECT_EQ(third.to_decimal().to_int64(), 111); +} + +TEST(JSON_decimal, negative_in_object) { + const sourcemeta::core::Decimal value{"-67.89"}; + const sourcemeta::core::JSON document{ + {"test", sourcemeta::core::JSON{value}}}; + EXPECT_TRUE(document.at("test").is_decimal()); + EXPECT_EQ(document.at("test").to_decimal().to_string(), "-67.89"); +} + +TEST(JSON_decimal, negative_copy_into_array) { + const sourcemeta::core::Decimal value{"-67.89"}; + const sourcemeta::core::JSON decimal_json{value}; + sourcemeta::core::JSON document{sourcemeta::core::JSON::make_array()}; + document.push_back(decimal_json); + EXPECT_TRUE(document.at(0).is_decimal()); + EXPECT_EQ(document.at(0).to_decimal().to_string(), "-67.89"); +} + +TEST(JSON_decimal, less_than_decimal_decimal) { + const sourcemeta::core::JSON value1{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON value2{sourcemeta::core::Decimal{"5.7"}}; + EXPECT_TRUE(value1 < value2); + EXPECT_FALSE(value2 < value1); + EXPECT_FALSE(value1 < value1); +} + +TEST(JSON_decimal, less_than_or_equal_decimal_decimal) { + const sourcemeta::core::JSON value1{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON value2{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON value3{sourcemeta::core::Decimal{"3.2"}}; + EXPECT_TRUE(value1 <= value2); + EXPECT_FALSE(value2 <= value1); + EXPECT_TRUE(value1 <= value3); +} + +TEST(JSON_decimal, greater_than_decimal_decimal) { + const sourcemeta::core::JSON value1{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON value2{sourcemeta::core::Decimal{"3.2"}}; + EXPECT_TRUE(value1 > value2); + EXPECT_FALSE(value2 > value1); + EXPECT_FALSE(value1 > value1); +} + +TEST(JSON_decimal, greater_than_or_equal_decimal_decimal) { + const sourcemeta::core::JSON value1{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON value2{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON value3{sourcemeta::core::Decimal{"5.7"}}; + EXPECT_TRUE(value1 >= value2); + EXPECT_FALSE(value2 >= value1); + EXPECT_TRUE(value1 >= value3); +} + +TEST(JSON_decimal, less_than_decimal_integer) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON integer{5}; + EXPECT_TRUE(decimal < integer); + EXPECT_FALSE(integer < decimal); +} + +TEST(JSON_decimal, less_than_or_equal_decimal_integer) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON integer{5}; + const sourcemeta::core::JSON equal{sourcemeta::core::Decimal{"5"}}; + EXPECT_TRUE(decimal <= integer); + EXPECT_FALSE(integer <= decimal); + EXPECT_TRUE(equal <= integer); +} + +TEST(JSON_decimal, greater_than_decimal_integer) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON integer{3}; + EXPECT_TRUE(decimal > integer); + EXPECT_FALSE(integer > decimal); +} + +TEST(JSON_decimal, greater_than_or_equal_decimal_integer) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON integer{3}; + const sourcemeta::core::JSON equal{sourcemeta::core::Decimal{"3"}}; + EXPECT_TRUE(decimal >= integer); + EXPECT_FALSE(integer >= decimal); + EXPECT_TRUE(equal >= integer); +} + +TEST(JSON_decimal, less_than_decimal_real) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON real{5.5}; + EXPECT_TRUE(decimal < real); + EXPECT_FALSE(real < decimal); +} + +TEST(JSON_decimal, less_than_or_equal_decimal_real) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON real{5.5}; + const sourcemeta::core::JSON equal{sourcemeta::core::Decimal{"5.5"}}; + EXPECT_TRUE(decimal <= real); + EXPECT_FALSE(real <= decimal); + EXPECT_TRUE(equal <= real); +} + +TEST(JSON_decimal, greater_than_decimal_real) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON real{3.5}; + EXPECT_TRUE(decimal > real); + EXPECT_FALSE(real > decimal); +} + +TEST(JSON_decimal, greater_than_or_equal_decimal_real) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON real{3.5}; + const sourcemeta::core::JSON equal{sourcemeta::core::Decimal{"3.5"}}; + EXPECT_TRUE(decimal >= real); + EXPECT_FALSE(real >= decimal); + EXPECT_TRUE(equal >= real); +} + +TEST(JSON_decimal, addition_decimal_decimal) { + const sourcemeta::core::JSON value1{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON value2{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON result{value1 + value2}; + EXPECT_TRUE(result.is_decimal()); + EXPECT_EQ(result.to_decimal().to_string(), "8.9"); +} + +TEST(JSON_decimal, subtraction_decimal_decimal) { + const sourcemeta::core::JSON value1{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON value2{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON result{value1 - value2}; + EXPECT_TRUE(result.is_decimal()); + EXPECT_EQ(result.to_decimal().to_string(), "2.5"); +} + +TEST(JSON_decimal, addition_assignment_decimal_decimal) { + sourcemeta::core::JSON value1{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON value2{sourcemeta::core::Decimal{"5.7"}}; + value1 += value2; + EXPECT_TRUE(value1.is_decimal()); + EXPECT_EQ(value1.to_decimal().to_string(), "8.9"); +} + +TEST(JSON_decimal, subtraction_assignment_decimal_decimal) { + sourcemeta::core::JSON value1{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON value2{sourcemeta::core::Decimal{"3.2"}}; + value1 -= value2; + EXPECT_TRUE(value1.is_decimal()); + EXPECT_EQ(value1.to_decimal().to_string(), "2.5"); +} + +TEST(JSON_decimal, addition_decimal_integer) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON integer{5}; + const sourcemeta::core::JSON result{decimal + integer}; + EXPECT_TRUE(result.is_decimal()); + EXPECT_EQ(result.to_decimal().to_string(), "8.2"); +} + +TEST(JSON_decimal, subtraction_decimal_integer) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON integer{3}; + const sourcemeta::core::JSON result{decimal - integer}; + EXPECT_TRUE(result.is_decimal()); + EXPECT_EQ(result.to_decimal().to_string(), "2.7"); +} + +TEST(JSON_decimal, addition_assignment_decimal_integer) { + sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON integer{5}; + decimal += integer; + EXPECT_TRUE(decimal.is_decimal()); + EXPECT_EQ(decimal.to_decimal().to_string(), "8.2"); +} + +TEST(JSON_decimal, subtraction_assignment_decimal_integer) { + sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON integer{3}; + decimal -= integer; + EXPECT_TRUE(decimal.is_decimal()); + EXPECT_EQ(decimal.to_decimal().to_string(), "2.7"); +} + +TEST(JSON_decimal, addition_decimal_real) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON real{5.5}; + const sourcemeta::core::JSON result{decimal + real}; + EXPECT_TRUE(result.is_decimal()); + EXPECT_EQ(result.to_decimal().to_string(), "8.7"); +} + +TEST(JSON_decimal, subtraction_decimal_real) { + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON real{3.5}; + const sourcemeta::core::JSON result{decimal - real}; + EXPECT_TRUE(result.is_decimal()); + EXPECT_EQ(result.to_decimal().to_string(), "2.2"); +} + +TEST(JSON_decimal, addition_assignment_decimal_real) { + sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON real{5.5}; + decimal += real; + EXPECT_TRUE(decimal.is_decimal()); + EXPECT_EQ(decimal.to_decimal().to_string(), "8.7"); +} + +TEST(JSON_decimal, subtraction_assignment_decimal_real) { + sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON real{3.5}; + decimal -= real; + EXPECT_TRUE(decimal.is_decimal()); + EXPECT_EQ(decimal.to_decimal().to_string(), "2.2"); +} + +TEST(JSON_decimal, is_number_positive) { + const sourcemeta::core::JSON document{sourcemeta::core::Decimal{"5.7"}}; + EXPECT_TRUE(document.is_number()); +} + +TEST(JSON_decimal, is_number_negative) { + const sourcemeta::core::JSON document{sourcemeta::core::Decimal{"-3.2"}}; + EXPECT_TRUE(document.is_number()); +} + +TEST(JSON_decimal, is_number_zero) { + const sourcemeta::core::JSON document{sourcemeta::core::Decimal{"0"}}; + EXPECT_TRUE(document.is_number()); +} + +TEST(JSON_decimal, is_number_very_large) { + const sourcemeta::core::JSON document{ + sourcemeta::core::Decimal{"123456789012345678901234567890.5"}}; + EXPECT_TRUE(document.is_number()); +} + +TEST(JSON_decimal, is_positive_positive) { + const sourcemeta::core::JSON document{sourcemeta::core::Decimal{"5.7"}}; + EXPECT_TRUE(document.is_positive()); +} + +TEST(JSON_decimal, is_positive_negative) { + const sourcemeta::core::JSON document{sourcemeta::core::Decimal{"-3.2"}}; + EXPECT_FALSE(document.is_positive()); +} + +TEST(JSON_decimal, is_positive_zero) { + const sourcemeta::core::JSON document{sourcemeta::core::Decimal{"0"}}; + EXPECT_TRUE(document.is_positive()); +} + +TEST(JSON_decimal, is_positive_very_small_positive) { + const sourcemeta::core::JSON document{ + sourcemeta::core::Decimal{"0.0000000001"}}; + EXPECT_TRUE(document.is_positive()); +} + +TEST(JSON_decimal, is_positive_very_small_negative) { + const sourcemeta::core::JSON document{ + sourcemeta::core::Decimal{"-0.0000000001"}}; + EXPECT_FALSE(document.is_positive()); +} diff --git a/test/json/json_integer_test.cc b/test/json/json_integer_test.cc index a24ea12fb..6893ac3d0 100644 --- a/test/json/json_integer_test.cc +++ b/test/json/json_integer_test.cc @@ -73,3 +73,67 @@ TEST(JSON_integer, fast_hash_1234567) { const sourcemeta::core::JSON document{1234567}; EXPECT_EQ(document.fast_hash(), 139); } + +TEST(JSON_integer, less_than_integer_decimal) { + const sourcemeta::core::JSON integer{3}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + EXPECT_TRUE(integer < decimal); + EXPECT_FALSE(decimal < integer); +} + +TEST(JSON_integer, less_than_or_equal_integer_decimal) { + const sourcemeta::core::JSON integer{3}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON equal{sourcemeta::core::Decimal{"3"}}; + EXPECT_TRUE(integer <= decimal); + EXPECT_FALSE(decimal <= integer); + EXPECT_TRUE(integer <= equal); +} + +TEST(JSON_integer, greater_than_integer_decimal) { + const sourcemeta::core::JSON integer{5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + EXPECT_TRUE(integer > decimal); + EXPECT_FALSE(decimal > integer); +} + +TEST(JSON_integer, greater_than_or_equal_integer_decimal) { + const sourcemeta::core::JSON integer{5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON equal{sourcemeta::core::Decimal{"5"}}; + EXPECT_TRUE(integer >= decimal); + EXPECT_FALSE(decimal >= integer); + EXPECT_TRUE(integer >= equal); +} + +TEST(JSON_integer, addition_integer_decimal) { + const sourcemeta::core::JSON integer{3}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON result{integer + decimal}; + EXPECT_TRUE(result.is_decimal()); + EXPECT_EQ(result.to_decimal().to_string(), "8.7"); +} + +TEST(JSON_integer, subtraction_integer_decimal) { + const sourcemeta::core::JSON integer{5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON result{integer - decimal}; + EXPECT_TRUE(result.is_decimal()); + EXPECT_EQ(result.to_decimal().to_string(), "1.8"); +} + +TEST(JSON_integer, addition_assignment_integer_decimal) { + sourcemeta::core::JSON integer{3}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + integer += decimal; + EXPECT_TRUE(integer.is_decimal()); + EXPECT_EQ(integer.to_decimal().to_string(), "8.7"); +} + +TEST(JSON_integer, subtraction_assignment_integer_decimal) { + sourcemeta::core::JSON integer{5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + integer -= decimal; + EXPECT_TRUE(integer.is_decimal()); + EXPECT_EQ(integer.to_decimal().to_string(), "1.8"); +} diff --git a/test/json/json_parse_callback_test.cc b/test/json/json_parse_callback_test.cc index d7c92c104..35c0ad2c6 100644 --- a/test/json/json_parse_callback_test.cc +++ b/test/json/json_parse_callback_test.cc @@ -299,3 +299,95 @@ TEST(JSON_parse_callback, read_json_stub_valid) { EXPECT_TRACE(2, Post, Integer, 1, 10, sourcemeta::core::JSON{1}); EXPECT_TRACE(3, Post, Object, 1, 12, sourcemeta::core::read_json(input)); } + +TEST(JSON_parse_callback, decimal_simple) { + const auto input{"9223372036854776000"}; + PARSE_WITH_TRACES(document, input, 2); + EXPECT_TRACE(0, Pre, Decimal, 1, 1, sourcemeta::core::JSON{nullptr}); + EXPECT_TRACE( + 1, Post, Decimal, 1, 19, + sourcemeta::core::JSON{sourcemeta::core::Decimal{"9223372036854776000"}}); +} + +TEST(JSON_parse_callback, decimal_negative) { + const auto input{"-9223372036854776000"}; + PARSE_WITH_TRACES(document, input, 2); + EXPECT_TRACE(0, Pre, Decimal, 1, 1, sourcemeta::core::JSON{nullptr}); + EXPECT_TRACE(1, Post, Decimal, 1, 20, + sourcemeta::core::JSON{ + sourcemeta::core::Decimal{"-9223372036854776000"}}); +} + +TEST(JSON_parse_callback, decimal_in_array) { + const auto input{"[1, 9223372036854776000, 3]"}; + PARSE_WITH_TRACES(document, input, 8); + EXPECT_TRACE(0, Pre, Array, 1, 1, sourcemeta::core::JSON{nullptr}); + EXPECT_TRACE(1, Pre, Integer, 1, 2, sourcemeta::core::JSON{0}); + EXPECT_TRACE(2, Post, Integer, 1, 2, sourcemeta::core::JSON{1}); + EXPECT_TRACE(3, Pre, Decimal, 1, 5, sourcemeta::core::JSON{1}); + EXPECT_TRACE( + 4, Post, Decimal, 1, 23, + sourcemeta::core::JSON{sourcemeta::core::Decimal{"9223372036854776000"}}); + EXPECT_TRACE(5, Pre, Integer, 1, 26, sourcemeta::core::JSON{2}); + EXPECT_TRACE(6, Post, Integer, 1, 26, sourcemeta::core::JSON{3}); + EXPECT_TRACE(7, Post, Array, 1, 27, sourcemeta::core::parse_json(input)); +} + +TEST(JSON_parse_callback, decimal_in_object) { + const auto input{"{\"big\":9223372036854776000}"}; + PARSE_WITH_TRACES(document, input, 4); + EXPECT_TRACE(0, Pre, Object, 1, 1, sourcemeta::core::JSON{nullptr}); + EXPECT_TRACE(1, Pre, Decimal, 1, 2, sourcemeta::core::JSON{"big"}); + EXPECT_TRACE( + 2, Post, Decimal, 1, 26, + sourcemeta::core::JSON{sourcemeta::core::Decimal{"9223372036854776000"}}); + EXPECT_TRACE(3, Post, Object, 1, 27, sourcemeta::core::parse_json(input)); +} + +TEST(JSON_parse_callback, decimals_mixed_in_object) { + const auto input{"{\"big\":9223372036854776000,\"small\":42}"}; + PARSE_WITH_TRACES(document, input, 6); + EXPECT_TRACE(0, Pre, Object, 1, 1, sourcemeta::core::JSON{nullptr}); + EXPECT_TRACE(1, Pre, Decimal, 1, 2, sourcemeta::core::JSON{"big"}); + EXPECT_TRACE( + 2, Post, Decimal, 1, 26, + sourcemeta::core::JSON{sourcemeta::core::Decimal{"9223372036854776000"}}); + EXPECT_TRACE(3, Pre, Integer, 1, 28, sourcemeta::core::JSON{"small"}); + EXPECT_TRACE(4, Post, Integer, 1, 37, sourcemeta::core::JSON{42}); + EXPECT_TRACE(5, Post, Object, 1, 38, sourcemeta::core::parse_json(input)); +} + +TEST(JSON_parse_callback, decimal_big_real) { + const auto input{"3.14159265358979323846264338327950288419716939937510"}; + PARSE_WITH_TRACES(document, input, 2); + EXPECT_TRACE(0, Pre, Real, 1, 1, sourcemeta::core::JSON{nullptr}); + EXPECT_TRACE(1, Post, Real, 1, 52, + sourcemeta::core::JSON{ + 3.14159265358979323846264338327950288419716939937510}); +} + +TEST(JSON_parse_callback, nested_decimals) { + const auto input{"{\"data\":[{\"big_int\":9223372036854776000}]}"}; + PARSE_WITH_TRACES(document, input, 8); + EXPECT_TRACE(0, Pre, Object, 1, 1, sourcemeta::core::JSON{nullptr}); + EXPECT_TRACE(1, Pre, Array, 1, 2, sourcemeta::core::JSON{"data"}); + EXPECT_TRACE(2, Pre, Object, 1, 10, sourcemeta::core::JSON{0}); + EXPECT_TRACE(3, Pre, Decimal, 1, 11, sourcemeta::core::JSON{"big_int"}); + EXPECT_TRACE( + 4, Post, Decimal, 1, 39, + sourcemeta::core::JSON{sourcemeta::core::Decimal{"9223372036854776000"}}); + EXPECT_TRACE(5, Post, Object, 1, 40, + sourcemeta::core::parse_json(input).at("data").at(0)); + EXPECT_TRACE(6, Post, Array, 1, 41, + sourcemeta::core::parse_json(input).at("data")); + EXPECT_TRACE(7, Post, Object, 1, 42, sourcemeta::core::parse_json(input)); +} + +TEST(JSON_parse_callback, read_json_stub_bigint) { + const auto input{std::filesystem::path{TEST_DIRECTORY} / "stub_bigint.json"}; + READ_WITH_TRACES(document, input, 2); + EXPECT_TRACE(0, Pre, Decimal, 1, 1, sourcemeta::core::JSON{nullptr}); + EXPECT_TRACE( + 1, Post, Decimal, 1, 19, + sourcemeta::core::JSON{sourcemeta::core::Decimal{"9223372036854776000"}}); +} diff --git a/test/json/json_parse_error_test.cc b/test/json/json_parse_error_test.cc index 3c98ebbdc..6bb48ddd5 100644 --- a/test/json/json_parse_error_test.cc +++ b/test/json/json_parse_error_test.cc @@ -24,13 +24,6 @@ __EXPECT_PARSE_ERROR(input, expected_line, expected_column, JSONParseError, \ "Failed to parse the JSON document"); -#define EXPECT_PARSE_ERROR_INTEGER_LIMIT(input, expected_line, \ - expected_column) \ - __EXPECT_PARSE_ERROR(input, expected_line, expected_column, \ - JSONParseIntegerLimitError, \ - "The JSON value is not representable by the IETF RFC " \ - "8259 interoperable signed integer range"); - TEST(JSON_parse_error, boolean_true_invalid) { std::istringstream input{"trrue"}; EXPECT_PARSE_ERROR(input, 1, 3); @@ -533,16 +526,6 @@ TEST(JSON_parse_error, integer_negative_with_leading_zero) { EXPECT_PARSE_ERROR(input, 1, 3); } -TEST(JSON_parse_error, integer_slightly_over_64_bit_positive_limit) { - std::istringstream input{"9223372036854776000"}; - EXPECT_PARSE_ERROR_INTEGER_LIMIT(input, 1, 1); -} - -TEST(JSON_parse_error, integer_slightly_over_64_bit_positive_limit_in_object) { - std::istringstream input{"{\"foo\":9223372036854776000}"}; - EXPECT_PARSE_ERROR_INTEGER_LIMIT(input, 1, 8); -} - TEST(JSON_parse_error, integer_negative_with_leading_zero_and_space) { std::istringstream input{"[-0 5]"}; EXPECT_PARSE_ERROR(input, 1, 5); @@ -568,11 +551,31 @@ TEST(JSON_parse_error, multiple_leading_zeroes_real_number) { EXPECT_PARSE_ERROR(input, 1, 4); } -TEST(JSON_parse_error, huge_negative_exponent) { - std::istringstream input{"123.456e-789"}; +TEST(JSON_parse_error, decimal_invalid_standalone_exponent) { + std::istringstream input{"e10"}; EXPECT_PARSE_ERROR(input, 1, 1); } +TEST(JSON_parse_error, decimal_invalid_standalone_negative_sign) { + std::istringstream input{"-"}; + EXPECT_PARSE_ERROR(input, 1, 2); +} + +TEST(JSON_parse_error, decimal_invalid_standalone_decimal_point) { + std::istringstream input{"."}; + EXPECT_PARSE_ERROR(input, 1, 1); +} + +TEST(JSON_parse_error, decimal_exponent_no_digits) { + std::istringstream input{"1e"}; + EXPECT_PARSE_ERROR(input, 1, 3); +} + +TEST(JSON_parse_error, decimal_exponent_sign_no_digits) { + std::istringstream input{"1e+"}; + EXPECT_PARSE_ERROR(input, 1, 4); +} + TEST(JSON_parse_error, exponential_notation_error_double_upper_e) { std::istringstream input{"3EE2"}; EXPECT_PARSE_ERROR(input, 1, 3); diff --git a/test/json/json_parse_roundtrip_test.cc b/test/json/json_parse_roundtrip_test.cc new file mode 100644 index 000000000..2140ff261 --- /dev/null +++ b/test/json/json_parse_roundtrip_test.cc @@ -0,0 +1,65 @@ +#include + +#include + +#include + +TEST(JSON_parse_roundtrip, decimal_large_integer) { + const sourcemeta::core::JSON original{ + sourcemeta::core::Decimal{"123456789012345678901234567890"}}; + std::ostringstream stream; + sourcemeta::core::stringify(original, stream); + const auto parsed{sourcemeta::core::parse_json(stream.str())}; + EXPECT_EQ(original, parsed); + EXPECT_TRUE(parsed.is_decimal()); + EXPECT_EQ(parsed.to_decimal().to_string(), "123456789012345678901234567890"); +} + +TEST(JSON_parse_roundtrip, decimal_exponential_overflow_positive) { + const sourcemeta::core::JSON original{ + sourcemeta::core::Decimal{"1.23456789012345678901234567890e309"}}; + std::ostringstream stream; + sourcemeta::core::stringify(original, stream); + const auto parsed{sourcemeta::core::parse_json(stream.str())}; + EXPECT_EQ(original, parsed); + EXPECT_TRUE(parsed.is_decimal()); +} + +TEST(JSON_parse_roundtrip, decimal_exponential_overflow_large) { + const sourcemeta::core::JSON original{ + sourcemeta::core::Decimal{"9.87654321098765432109876543210e320"}}; + std::ostringstream stream; + sourcemeta::core::stringify(original, stream); + const auto parsed{sourcemeta::core::parse_json(stream.str())}; + EXPECT_EQ(original, parsed); + EXPECT_TRUE(parsed.is_decimal()); +} + +TEST(JSON_parse_roundtrip, decimal_exponential_very_large_exponent) { + const sourcemeta::core::JSON original{sourcemeta::core::Decimal{"1.5e400"}}; + std::ostringstream stream; + sourcemeta::core::stringify(original, stream); + const auto parsed{sourcemeta::core::parse_json(stream.str())}; + EXPECT_EQ(original, parsed); + EXPECT_TRUE(parsed.is_decimal()); +} + +TEST(JSON_parse_roundtrip, decimal_exponential_negative_overflow) { + const sourcemeta::core::JSON original{ + sourcemeta::core::Decimal{"-2.718281828459045235360287471352e310"}}; + std::ostringstream stream; + sourcemeta::core::stringify(original, stream); + const auto parsed{sourcemeta::core::parse_json(stream.str())}; + EXPECT_EQ(original, parsed); + EXPECT_TRUE(parsed.is_decimal()); +} + +TEST(JSON_parse_roundtrip, + decimal_exponential_small_coefficient_large_exponent) { + const sourcemeta::core::JSON original{sourcemeta::core::Decimal{"3.14e350"}}; + std::ostringstream stream; + sourcemeta::core::stringify(original, stream); + const auto parsed{sourcemeta::core::parse_json(stream.str())}; + EXPECT_EQ(original, parsed); + EXPECT_TRUE(parsed.is_decimal()); +} diff --git a/test/json/json_parse_test.cc b/test/json/json_parse_test.cc index 8ddd7957d..83d900a62 100644 --- a/test/json/json_parse_test.cc +++ b/test/json/json_parse_test.cc @@ -1369,3 +1369,128 @@ TEST(JSON_parse, read_file) { EXPECT_TRUE(document.at("foo").is_integer()); EXPECT_EQ(document.at("foo").to_integer(), 1); } + +TEST(JSON_parse, big_integer_beyond_64_bit) { + std::istringstream input{"9223372036854776000"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_string(), "9223372036854776000"); +} + +TEST(JSON_parse, big_negative_integer_beyond_64_bit) { + std::istringstream input{"-9223372036854776000"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_string(), "-9223372036854776000"); +} + +TEST(JSON_parse, very_large_integer) { + std::istringstream input{"123456789012345678901234567890"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_string(), + "123456789012345678901234567890"); +} + +TEST(JSON_parse, big_integer_in_array) { + std::istringstream input{"[1, 9223372036854776000, 3]"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_array()); + EXPECT_EQ(document.size(), 3); + EXPECT_TRUE(document.at(0).is_integer()); + EXPECT_EQ(document.at(0).to_integer(), 1); + EXPECT_TRUE(document.at(1).is_decimal()); + EXPECT_EQ(document.at(1).to_decimal().to_string(), "9223372036854776000"); + EXPECT_TRUE(document.at(2).is_integer()); + EXPECT_EQ(document.at(2).to_integer(), 3); +} + +TEST(JSON_parse, big_integer_in_object) { + std::istringstream input{"{\"big\":9223372036854776000,\"small\":42}"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_object()); + EXPECT_EQ(document.size(), 2); + EXPECT_TRUE(document.at("big").is_decimal()); + EXPECT_EQ(document.at("big").to_decimal().to_string(), "9223372036854776000"); + EXPECT_TRUE(document.at("small").is_integer()); + EXPECT_EQ(document.at("small").to_integer(), 42); +} + +TEST(JSON_parse, big_real_number) { + std::istringstream input{"1.234567890123456789012345678901234567890"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_real()); + EXPECT_EQ(document.to_real(), 1.234567890123456789012345678901234567890); +} + +TEST(JSON_parse, very_small_real_number) { + std::istringstream input{"0.000000000000000000000000000001"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_real()); + EXPECT_EQ(document.to_real(), 0.000000000000000000000000000001); +} + +TEST(JSON_parse, big_real_in_array) { + std::istringstream input{ + "[1.5, 1.234567890123456789012345678901234567890, 2.5]"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_array()); + EXPECT_EQ(document.size(), 3); + EXPECT_TRUE(document.at(0).is_real()); + EXPECT_EQ(document.at(0).to_real(), 1.5); + EXPECT_TRUE(document.at(1).is_real()); + EXPECT_EQ(document.at(1).to_real(), + 1.234567890123456789012345678901234567890); + EXPECT_TRUE(document.at(2).is_real()); + EXPECT_EQ(document.at(2).to_real(), 2.5); +} + +TEST(JSON_parse, big_real_in_object) { + std::istringstream input{ + "{\"pi\":3.14159265358979323846264338327950288419716939937510}"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_object()); + EXPECT_EQ(document.size(), 1); + EXPECT_TRUE(document.at("pi").is_real()); + EXPECT_EQ(document.at("pi").to_real(), + 3.14159265358979323846264338327950288419716939937510); +} + +TEST(JSON_parse, big_number_with_exponent) { + std::istringstream input{"1.5e10"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_real()); + EXPECT_DOUBLE_EQ(document.to_real(), 1.5e10); +} + +TEST(JSON_parse, big_number_with_negative_exponent) { + std::istringstream input{"1.5e-10"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_real()); + EXPECT_DOUBLE_EQ(document.to_real(), 1.5e-10); +} + +TEST(JSON_parse, read_json_stub_bigint) { + const sourcemeta::core::JSON document{sourcemeta::core::read_json( + std::filesystem::path{TEST_DIRECTORY} / "stub_bigint.json")}; + EXPECT_TRUE(document.is_decimal()); + EXPECT_EQ(document.to_decimal().to_string(), "9223372036854776000"); +} + +TEST(JSON_parse, nested_big_integers_and_reals) { + std::istringstream input{ + "{\"data\":[{\"big_int\":9223372036854776000," + "\"big_real\":3.14159265358979323846264338327950288419716939937510}]}"}; + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(input); + EXPECT_TRUE(document.is_object()); + EXPECT_TRUE(document.at("data").is_array()); + EXPECT_EQ(document.at("data").size(), 1); + EXPECT_TRUE(document.at("data").at(0).is_object()); + EXPECT_EQ(document.at("data").at(0).size(), 2); + EXPECT_TRUE(document.at("data").at(0).at("big_int").is_decimal()); + EXPECT_EQ(document.at("data").at(0).at("big_int").to_decimal().to_string(), + "9223372036854776000"); + EXPECT_TRUE(document.at("data").at(0).at("big_real").is_real()); + EXPECT_EQ(document.at("data").at(0).at("big_real").to_real(), + 3.14159265358979323846264338327950288419716939937510); +} diff --git a/test/json/json_prettify_test.cc b/test/json/json_prettify_test.cc index 806505ccb..84113ec2a 100644 --- a/test/json/json_prettify_test.cc +++ b/test/json/json_prettify_test.cc @@ -501,3 +501,195 @@ TEST(JSON_prettify, object_reorder_with_4_spaces) { EXPECT_EQ(stream.str(), "{\n \"bar\": 2,\n \"baz\": 3,\n \"foo\": 1\n}"); } + +TEST(JSON_prettify, decimal_positive_integer) { + const sourcemeta::core::Decimal value{12345}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "12345"); +} + +TEST(JSON_prettify, decimal_negative_integer) { + const sourcemeta::core::Decimal value{-67890}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "-67890"); +} + +TEST(JSON_prettify, decimal_zero) { + const sourcemeta::core::Decimal value{0}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "0"); +} + +TEST(JSON_prettify, decimal_fractional) { + const sourcemeta::core::Decimal value{"3.14159"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "3.14159"); +} + +TEST(JSON_prettify, decimal_negative_fractional) { + const sourcemeta::core::Decimal value{"-2.71828"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "-2.71828"); +} + +TEST(JSON_prettify, decimal_large_integer) { + const sourcemeta::core::Decimal value{"123456789012345678901234567890"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "123456789012345678901234567890"); +} + +TEST(JSON_prettify, decimal_large_negative_integer) { + const sourcemeta::core::Decimal value{"-987654321098765432109876543210"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "-987654321098765432109876543210"); +} + +TEST(JSON_prettify, decimal_high_precision_fractional) { + const sourcemeta::core::Decimal value{ + "3.141592653589793238462643383279502884197"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "3.141592653589793238462643383279502884197"); +} + +TEST(JSON_prettify, decimal_very_small_fractional) { + const sourcemeta::core::Decimal value{"0.000000000000000000000000000001"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "1e-30"); +} + +TEST(JSON_prettify, decimal_scientific_notation) { + const sourcemeta::core::Decimal value{"1.23e10"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "12.3e+9"); +} + +TEST(JSON_prettify, decimal_in_array) { + const sourcemeta::core::Decimal value1{100}; + const sourcemeta::core::Decimal value2{"999.999"}; + const sourcemeta::core::Decimal value3{"-42"}; + const sourcemeta::core::JSON document{sourcemeta::core::JSON{value1}, + sourcemeta::core::JSON{value2}, + sourcemeta::core::JSON{value3}}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "[ 100, 999.999, -42 ]"); +} + +TEST(JSON_prettify, decimal_large_numbers_in_array) { + const sourcemeta::core::Decimal value1{"123456789012345678901234567890"}; + const sourcemeta::core::Decimal value2{ + "98765432109876543210.98765432109876543210"}; + const sourcemeta::core::JSON document{sourcemeta::core::JSON{value1}, + sourcemeta::core::JSON{value2}}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), + "[ 123456789012345678901234567890, 98765432109876543210." + "98765432109876543210 ]"); +} + +TEST(JSON_prettify, decimal_nested_in_array) { + const sourcemeta::core::Decimal value1{"111.111"}; + const sourcemeta::core::Decimal value2{"222.222"}; + sourcemeta::core::JSON inner_array{sourcemeta::core::JSON{value1}, + sourcemeta::core::JSON{value2}}; + sourcemeta::core::JSON document{sourcemeta::core::JSON::Array{}}; + document.push_back(std::move(inner_array)); + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "[\n [ 111.111, 222.222 ]\n]"); +} + +TEST(JSON_prettify, decimal_in_object) { + const sourcemeta::core::Decimal value1{"12345"}; + const sourcemeta::core::Decimal value2{"-67.89"}; + sourcemeta::core::JSON document{ + {"integer", sourcemeta::core::JSON{value1}}, + {"fractional", sourcemeta::core::JSON{value2}}}; + document.reorder( + [](const auto &left, const auto &right) { return left < right; }); + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), + "{\n \"fractional\": -67.89,\n \"integer\": 12345\n}"); +} + +TEST(JSON_prettify, decimal_large_numbers_in_object) { + const sourcemeta::core::Decimal big_int{"999999999999999999999999999999"}; + const sourcemeta::core::Decimal big_real{ + "123456789.123456789123456789123456789"}; + sourcemeta::core::JSON document{ + {"bigInt", sourcemeta::core::JSON{big_int}}, + {"bigReal", sourcemeta::core::JSON{big_real}}}; + document.reorder( + [](const auto &left, const auto &right) { return left < right; }); + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), + "{\n \"bigInt\": 999999999999999999999999999999,\n \"bigReal\": " + "123456789.123456789123456789123456789\n}"); +} + +TEST(JSON_prettify, decimal_mixed_with_other_types_in_object) { + const sourcemeta::core::Decimal decimal_value{"3.14159"}; + sourcemeta::core::JSON document{ + {"string", sourcemeta::core::JSON{"hello"}}, + {"integer", sourcemeta::core::JSON{42}}, + {"decimal", sourcemeta::core::JSON{decimal_value}}, + {"boolean", sourcemeta::core::JSON{true}}}; + document.reorder( + [](const auto &left, const auto &right) { return left < right; }); + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), + "{\n \"boolean\": true,\n \"decimal\": 3.14159,\n \"integer\": " + "42,\n \"string\": \"hello\"\n}"); +} + +TEST(JSON_prettify, decimal_nested_in_object_with_array) { + const sourcemeta::core::Decimal value1{"111"}; + const sourcemeta::core::Decimal value2{"222.222"}; + sourcemeta::core::JSON array{sourcemeta::core::JSON::Array{}}; + array.push_back(sourcemeta::core::JSON{value1}); + array.push_back(sourcemeta::core::JSON{value2}); + const sourcemeta::core::JSON document{{"decimals", std::move(array)}}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream); + EXPECT_EQ(stream.str(), "{\n \"decimals\": [ 111, 222.222 ]\n}"); +} + +TEST(JSON_prettify, decimal_large_numbers_nested_with_indentation) { + const sourcemeta::core::Decimal big1{"111111111111111111111111111111"}; + const sourcemeta::core::Decimal big2{"222222222222222222222222222222"}; + sourcemeta::core::JSON inner_object{{"first", sourcemeta::core::JSON{big1}}, + {"second", sourcemeta::core::JSON{big2}}}; + inner_object.reorder( + [](const auto &left, const auto &right) { return left < right; }); + sourcemeta::core::JSON document{{"nested", std::move(inner_object)}}; + std::ostringstream stream; + sourcemeta::core::prettify(document, stream, 4); + EXPECT_EQ(stream.str(), + "{\n \"nested\": {\n \"first\": " + "111111111111111111111111111111,\n \"second\": " + "222222222222222222222222222222\n }\n}"); +} diff --git a/test/json/json_real_test.cc b/test/json/json_real_test.cc index e0dd86a47..dcbc24939 100644 --- a/test/json/json_real_test.cc +++ b/test/json/json_real_test.cc @@ -97,3 +97,67 @@ TEST(JSON_real, fast_hash_minus_0_0) { const sourcemeta::core::JSON document{0.0}; EXPECT_EQ(document.fast_hash(), 5); } + +TEST(JSON_real, less_than_real_decimal) { + const sourcemeta::core::JSON real{3.5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + EXPECT_TRUE(real < decimal); + EXPECT_FALSE(decimal < real); +} + +TEST(JSON_real, less_than_or_equal_real_decimal) { + const sourcemeta::core::JSON real{3.5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON equal{sourcemeta::core::Decimal{"3.5"}}; + EXPECT_TRUE(real <= decimal); + EXPECT_FALSE(decimal <= real); + EXPECT_TRUE(real <= equal); +} + +TEST(JSON_real, greater_than_real_decimal) { + const sourcemeta::core::JSON real{5.5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + EXPECT_TRUE(real > decimal); + EXPECT_FALSE(decimal > real); +} + +TEST(JSON_real, greater_than_or_equal_real_decimal) { + const sourcemeta::core::JSON real{5.5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON equal{sourcemeta::core::Decimal{"5.5"}}; + EXPECT_TRUE(real >= decimal); + EXPECT_FALSE(decimal >= real); + EXPECT_TRUE(real >= equal); +} + +TEST(JSON_real, addition_real_decimal) { + const sourcemeta::core::JSON real{3.5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + const sourcemeta::core::JSON result{real + decimal}; + EXPECT_TRUE(result.is_decimal()); + EXPECT_EQ(result.to_decimal().to_string(), "9.2"); +} + +TEST(JSON_real, subtraction_real_decimal) { + const sourcemeta::core::JSON real{5.5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + const sourcemeta::core::JSON result{real - decimal}; + EXPECT_TRUE(result.is_decimal()); + EXPECT_EQ(result.to_decimal().to_string(), "2.3"); +} + +TEST(JSON_real, addition_assignment_real_decimal) { + sourcemeta::core::JSON real{3.5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"5.7"}}; + real += decimal; + EXPECT_TRUE(real.is_decimal()); + EXPECT_EQ(real.to_decimal().to_string(), "9.2"); +} + +TEST(JSON_real, subtraction_assignment_real_decimal) { + sourcemeta::core::JSON real{5.5}; + const sourcemeta::core::JSON decimal{sourcemeta::core::Decimal{"3.2"}}; + real -= decimal; + EXPECT_TRUE(real.is_decimal()); + EXPECT_EQ(real.to_decimal().to_string(), "2.3"); +} diff --git a/test/json/json_stringify_test.cc b/test/json/json_stringify_test.cc index 35890286f..72a0a487d 100644 --- a/test/json/json_stringify_test.cc +++ b/test/json/json_stringify_test.cc @@ -473,3 +473,179 @@ TEST(JSON_stringify, escape_1F) { sourcemeta::core::stringify(document, stream); EXPECT_EQ(stream.str(), "\"foo\\u001Fbar\""); } + +TEST(JSON_stringify, decimal_positive_integer) { + const sourcemeta::core::Decimal value{12345}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "12345"); +} + +TEST(JSON_stringify, decimal_negative_integer) { + const sourcemeta::core::Decimal value{-67890}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "-67890"); +} + +TEST(JSON_stringify, decimal_zero) { + const sourcemeta::core::Decimal value{0}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "0"); +} + +TEST(JSON_stringify, decimal_fractional) { + const sourcemeta::core::Decimal value{"3.14159"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "3.14159"); +} + +TEST(JSON_stringify, decimal_negative_fractional) { + const sourcemeta::core::Decimal value{"-2.71828"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "-2.71828"); +} + +TEST(JSON_stringify, decimal_large_integer) { + const sourcemeta::core::Decimal value{"123456789012345678901234567890"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "123456789012345678901234567890"); +} + +TEST(JSON_stringify, decimal_large_negative_integer) { + const sourcemeta::core::Decimal value{"-987654321098765432109876543210"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "-987654321098765432109876543210"); +} + +TEST(JSON_stringify, decimal_high_precision_fractional) { + const sourcemeta::core::Decimal value{ + "3.141592653589793238462643383279502884197"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "3.141592653589793238462643383279502884197"); +} + +TEST(JSON_stringify, decimal_very_small_fractional) { + const sourcemeta::core::Decimal value{"0.000000000000000000000000000001"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "1e-30"); +} + +TEST(JSON_stringify, decimal_scientific_notation) { + const sourcemeta::core::Decimal value{"1.23e10"}; + const sourcemeta::core::JSON document{value}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "12.3e+9"); +} + +TEST(JSON_stringify, decimal_in_array) { + const sourcemeta::core::Decimal value1{100}; + const sourcemeta::core::Decimal value2{"999.999"}; + const sourcemeta::core::Decimal value3{"-42"}; + const sourcemeta::core::JSON document{sourcemeta::core::JSON{value1}, + sourcemeta::core::JSON{value2}, + sourcemeta::core::JSON{value3}}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "[100,999.999,-42]"); +} + +TEST(JSON_stringify, decimal_large_numbers_in_array) { + const sourcemeta::core::Decimal value1{"123456789012345678901234567890"}; + const sourcemeta::core::Decimal value2{ + "98765432109876543210.98765432109876543210"}; + const sourcemeta::core::JSON document{sourcemeta::core::JSON{value1}, + sourcemeta::core::JSON{value2}}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), + "[123456789012345678901234567890,98765432109876543210." + "98765432109876543210]"); +} + +TEST(JSON_stringify, decimal_nested_in_array) { + const sourcemeta::core::Decimal value1{"111.111"}; + const sourcemeta::core::Decimal value2{"222.222"}; + sourcemeta::core::JSON inner_array{sourcemeta::core::JSON{value1}, + sourcemeta::core::JSON{value2}}; + sourcemeta::core::JSON document{sourcemeta::core::JSON::Array{}}; + document.push_back(std::move(inner_array)); + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "[[111.111,222.222]]"); +} + +TEST(JSON_stringify, decimal_in_object) { + const sourcemeta::core::Decimal value1{"12345"}; + const sourcemeta::core::Decimal value2{"-67.89"}; + const sourcemeta::core::JSON document{ + {"integer", sourcemeta::core::JSON{value1}}, + {"fractional", sourcemeta::core::JSON{value2}}}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + const bool matches = + stream.str() == "{\"integer\":12345,\"fractional\":-67.89}" || + stream.str() == "{\"fractional\":-67.89,\"integer\":12345}"; + EXPECT_TRUE(matches); +} + +TEST(JSON_stringify, decimal_large_numbers_in_object) { + const sourcemeta::core::Decimal big_int{"999999999999999999999999999999"}; + const sourcemeta::core::Decimal big_real{ + "123456789.123456789123456789123456789"}; + sourcemeta::core::JSON document{ + {"bigInt", sourcemeta::core::JSON{big_int}}, + {"bigReal", sourcemeta::core::JSON{big_real}}}; + document.reorder( + [](const auto &left, const auto &right) { return left < right; }); + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), + "{\"bigInt\":999999999999999999999999999999,\"bigReal\":123456789." + "123456789123456789123456789}"); +} + +TEST(JSON_stringify, decimal_mixed_with_other_types_in_object) { + const sourcemeta::core::Decimal decimal_value{"3.14159"}; + sourcemeta::core::JSON document{ + {"string", sourcemeta::core::JSON{"hello"}}, + {"integer", sourcemeta::core::JSON{42}}, + {"decimal", sourcemeta::core::JSON{decimal_value}}, + {"boolean", sourcemeta::core::JSON{true}}}; + document.reorder( + [](const auto &left, const auto &right) { return left < right; }); + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), + "{\"boolean\":true,\"decimal\":3.14159,\"integer\":42,\"string\":" + "\"hello\"}"); +} + +TEST(JSON_stringify, decimal_nested_in_object_with_array) { + const sourcemeta::core::Decimal value1{"111"}; + const sourcemeta::core::Decimal value2{"222.222"}; + sourcemeta::core::JSON array{sourcemeta::core::JSON::Array{}}; + array.push_back(sourcemeta::core::JSON{value1}); + array.push_back(sourcemeta::core::JSON{value2}); + const sourcemeta::core::JSON document{{"decimals", std::move(array)}}; + std::ostringstream stream; + sourcemeta::core::stringify(document, stream); + EXPECT_EQ(stream.str(), "{\"decimals\":[111,222.222]}"); +} diff --git a/test/yaml/yaml_parse_callback_test.cc b/test/yaml/yaml_parse_callback_test.cc index 1f502fb4a..3f74c909b 100644 --- a/test/yaml/yaml_parse_callback_test.cc +++ b/test/yaml/yaml_parse_callback_test.cc @@ -450,3 +450,31 @@ TEST(YAML_parse_callback, yaml_nested_anchor_and_alias) { sourcemeta::core::parse_yaml("inner: &val 42\nref: *val")); EXPECT_TRACE(6, Post, Object, 4, 0, sourcemeta::core::parse_yaml(input)); } + +TEST(YAML_parse_callback, decimal_large_integer) { + const auto input{"123456789012345678901234567890"}; + PARSE_YAML_WITH_TRACES(document, input, 2); + EXPECT_TRACE(0, Pre, Decimal, 1, 1, sourcemeta::core::JSON{nullptr}); + EXPECT_TRACE(1, Post, Decimal, 1, 30, + sourcemeta::core::JSON{sourcemeta::core::Decimal{ + "123456789012345678901234567890"}}); +} + +TEST(YAML_parse_callback, decimal_high_precision_real) { + const auto input{"3.141592653589793238462643383279"}; + PARSE_YAML_WITH_TRACES(document, input, 2); + EXPECT_TRACE(0, Pre, Real, 1, 1, sourcemeta::core::JSON{nullptr}); + EXPECT_TRACE(1, Post, Real, 1, 32, + sourcemeta::core::JSON{3.141592653589793238462643383279}); +} + +TEST(YAML_parse_callback, decimal_in_object) { + const auto input{"large: 999999999999999999999999999999"}; + PARSE_YAML_WITH_TRACES(document, input, 4); + EXPECT_TRACE(0, Pre, Object, 1, 1, sourcemeta::core::JSON{nullptr}); + EXPECT_TRACE(1, Pre, Decimal, 1, 1, sourcemeta::core::JSON{"large"}); + EXPECT_TRACE(2, Post, Decimal, 1, 37, + sourcemeta::core::JSON{sourcemeta::core::Decimal{ + "999999999999999999999999999999"}}); + EXPECT_TRACE(3, Post, Object, 2, 0, sourcemeta::core::parse_yaml(input)); +} diff --git a/test/yaml/yaml_parse_test.cc b/test/yaml/yaml_parse_test.cc index 073acfec6..b1d1ec969 100644 --- a/test/yaml/yaml_parse_test.cc +++ b/test/yaml/yaml_parse_test.cc @@ -234,3 +234,52 @@ TEST(YAML_parse, multi_document_windows_line_endings) { EXPECT_EQ(stream.peek(), EOF); } + +TEST(YAML_parse, decimal_large_integer) { + const std::string input{"123456789012345678901234567890"}; + const auto result{sourcemeta::core::parse_yaml(input)}; + const sourcemeta::core::JSON expected{ + sourcemeta::core::Decimal{"123456789012345678901234567890"}}; + EXPECT_EQ(result, expected); + EXPECT_TRUE(result.is_decimal()); + EXPECT_EQ(result.to_decimal().to_string(), "123456789012345678901234567890"); +} + +TEST(YAML_parse, decimal_high_precision_real) { + const std::string input{"3.141592653589793238462643383279"}; + const auto result{sourcemeta::core::parse_yaml(input)}; + EXPECT_TRUE(result.is_real()); + EXPECT_EQ(result.to_real(), 3.141592653589793238462643383279); +} + +TEST(YAML_parse, decimal_exponential_notation) { + const std::string input{"1.234567890123456789012345678901e50"}; + const auto result{sourcemeta::core::parse_yaml(input)}; + EXPECT_TRUE(result.is_real()); +} + +TEST(YAML_parse, decimal_in_object) { + const std::string input{"large: 999999999999999999999999999999\n" + "precise: 2.718281828459045235360287471352"}; + const auto result{sourcemeta::core::parse_yaml(input)}; + + EXPECT_TRUE(result.is_object()); + EXPECT_TRUE(result.defines("large")); + EXPECT_TRUE(result.defines("precise")); + EXPECT_TRUE(result.at("large").is_decimal()); + EXPECT_TRUE(result.at("precise").is_real()); + EXPECT_EQ(result.at("large").to_decimal().to_string(), + "999999999999999999999999999999"); + EXPECT_EQ(result.at("precise").to_real(), 2.718281828459045235360287471352); +} + +TEST(YAML_parse, decimal_in_array) { + const std::string input{"- 123456789012345678901234567890\n" + "- 9.87654321098765432109876543210e100"}; + const auto result{sourcemeta::core::parse_yaml(input)}; + + EXPECT_TRUE(result.is_array()); + EXPECT_EQ(result.size(), 2); + EXPECT_TRUE(result.at(0).is_decimal()); + EXPECT_TRUE(result.at(1).is_real()); +}