diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8368373..321d995 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,8 +12,8 @@ jobs: fail-fast: false matrix: config: - - name: Ubuntu - os: ubuntu-latest + # - name: Ubuntu + # os: ubuntu-latest - name: macOS os: macos-latest diff --git a/include/matjson3.hpp b/include/matjson3.hpp index 8c85b20..920defe 100644 --- a/include/matjson3.hpp +++ b/include/matjson3.hpp @@ -22,9 +22,9 @@ namespace matjson { class ValueIterator; using Array = std::vector; - // TODO: fix this + // TODO: change these? using ParseError = std::string_view; - using PenisError = std::string_view; + using GenericError = std::string_view; static constexpr int NO_INDENTATION = 0; static constexpr int TAB_INDENTATION = -1; @@ -42,9 +42,17 @@ namespace matjson { Value(Array value); Value(std::nullptr_t); Value(double value); - // Value(std::int64_t value); - // Value(std::uint64_t value); Value(bool value); + explicit Value(std::intmax_t value); + explicit Value(std::uintmax_t value); + + template + requires std::is_integral_v && std::is_signed_v + Value(T value) : Value(static_cast(value)) {} + + template + requires std::is_integral_v && std::is_unsigned_v + Value(T value) : Value(static_cast(value)) {} template // Prevents implicit conversion from pointer to bool @@ -69,22 +77,22 @@ namespace matjson { /// Returns the value associated with the given key /// @param key Object key /// @return The value associated with the key, or an error if it does not exist. - geode::Result get(std::string_view key); + geode::Result get(std::string_view key); /// Returns the value associated with the given key /// @param key Object key /// @return The value associated with the key, or an error if it does not exist. - geode::Result get(std::string_view key) const; + geode::Result get(std::string_view key) const; /// Returns the value associated with the given index /// @param index Array index /// @return The value associated with the index, or an error if the index is out of bounds. - geode::Result get(size_t index); + geode::Result get(size_t index); /// Returns the value associated with the given index /// @param index Array index /// @return The value associated with the index, or an error if the index is out of bounds. - geode::Result get(size_t index) const; + geode::Result get(size_t index) const; /// Returns the value associated with the given key /// @param key Object key @@ -136,7 +144,7 @@ namespace matjson { bool operator<(Value const&) const; bool operator>(Value const&) const; - geode::Result dump(int indentationSize = 4) const; + geode::Result dump(int indentationSize = 4) const; Type type() const; @@ -170,6 +178,12 @@ namespace matjson { return this->type() == Type::Object; } + geode::Result asBool() const; + geode::Result asString() const; + geode::Result asInt() const; + geode::Result asUInt() const; + geode::Result asDouble() const; + std::optional getKey() const; }; @@ -203,6 +217,12 @@ namespace matjson { return std::forward(value); } } + + // For fmtlib, lol + inline std::string format_as(matjson::Value const& value) { + // let the exception through + return value.dump().unwrap(); + } } // allow destructuring diff --git a/src/dump.cpp b/src/dump.cpp index b812e6f..b27b6cb 100644 --- a/src/dump.cpp +++ b/src/dump.cpp @@ -1,11 +1,17 @@ #include "impl.hpp" #include +#include #include #include #include #include +// macOS and android still lack floating point std::to_chars support +#ifndef __cpp_lib_to_chars + #include "external/dragonbox.h" +#endif + using namespace matjson; using namespace geode; using namespace std::string_view_literals; @@ -36,21 +42,37 @@ void dumpJsonString(std::string_view str, std::string& out) { out.push_back('"'); } -Result dumpJsonNumber(ValueImpl const& impl, std::string& out) { - if (impl.isInteger()) { - out += std::to_string(impl.asInt()); +Result dumpJsonNumber(ValueImpl const& impl, std::string& out) { + if (impl.isInt()) { + out += std::to_string(impl.asNumber()); + } + else if (impl.isUInt()) { + out += std::to_string(impl.asNumber()); } else { - auto number = impl.asDouble(); + auto number = impl.asNumber(); if (std::isnan(number)) return Err("number cant be nan"); if (std::isinf(number)) return Err("number cant be infinity"); - // TODO: not be lazy here - out += std::to_string(number); +#ifndef __cpp_lib_to_chars + // use the dragonbox algorithm, code from + // https://github.com/abolz/Drachennest/blob/master/src/dragonbox.cc + std::array buffer; + auto* end = dragonbox::Dtoa(buffer.data(), number); + out += std::string_view(buffer.data(), end - buffer.data()); +#else + std::array buffer; + auto chars_result = std::to_chars(buffer.data(), buffer.data() + buffer.size(), number); + if (chars_result.ec == std::errc::value_too_large) [[unlikely]] { + // this should never happen, i think + return Err("number too large to convert to string"); + } + out += std::string_view(buffer.data(), chars_result.ptr - buffer.data()); +#endif } return Ok(); } -Result dumpImpl(Value const& value, std::string& out, int indentation, int depth) { +Result dumpImpl(Value const& value, std::string& out, int indentation, int depth) { auto& impl = ValueImpl::fromValue(value); switch (value.type()) { case Type::Null: { @@ -121,7 +143,7 @@ Result dumpImpl(Value const& value, std::string& out, int inde return Ok(); } -Result Value::dump(int indentationSize) const { +Result Value::dump(int indentationSize) const { std::string out; GEODE_UNWRAP(dumpImpl(*this, out, indentationSize, 0)); return Ok(out); diff --git a/src/impl.hpp b/src/impl.hpp index e8e8fd4..3712e22 100644 --- a/src/impl.hpp +++ b/src/impl.hpp @@ -4,10 +4,14 @@ #include #include +using std::intmax_t; +using std::size_t; +using std::uintmax_t; + class matjson::ValueImpl { Type m_type; std::optional m_key; - std::variant m_value; + std::variant m_value; public: template @@ -43,18 +47,6 @@ class matjson::ValueImpl { return std::get(m_value); } - double asDouble() const { - return std::get(m_value); - } - - std::int64_t asInt() const { - return std::get(m_value); - } - - bool isInteger() const { - return std::holds_alternative(m_value); - } - Array& asArray() { return std::get(m_value); } @@ -66,6 +58,23 @@ class matjson::ValueImpl { auto const& getVariant() { return m_value; } + + bool isInt() const { + return std::holds_alternative(m_value); + } + + bool isUInt() const { + return std::holds_alternative(m_value); + } + + template + NumberType asNumber() const { + if (std::holds_alternative(m_value)) + return static_cast(std::get(m_value)); + else if (std::holds_alternative(m_value)) + return static_cast(std::get(m_value)); + else return static_cast(std::get(m_value)); + } }; using ValuePtr = std::unique_ptr; diff --git a/src/parser.cpp b/src/parser.cpp index 58672e0..0d72ae0 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -175,6 +175,8 @@ Result parseString(StringStream& stream) noexcept { Result parseNumber(StringStream& stream) noexcept { std::string buffer; + bool isFloating = false; + bool isNegative = false; auto const addToBuffer = [&]() -> Result { GEODE_UNWRAP_INTO(char c, stream.take()); buffer.push_back(c); @@ -182,6 +184,7 @@ Result parseNumber(StringStream& stream) noexcept { }; GEODE_UNWRAP_INTO(char p, stream.peek()); if (p == '-') { + isNegative = true; GEODE_UNWRAP(addToBuffer()); } auto const takeDigits = [&]() -> Result { @@ -213,6 +216,7 @@ Result parseNumber(StringStream& stream) noexcept { // fraction GEODE_UNWRAP_INTO(p, stream.peek()); if (p == '.') { + isFloating = true; GEODE_UNWRAP(addToBuffer()); GEODE_UNWRAP(takeDigits()); @@ -222,6 +226,7 @@ Result parseNumber(StringStream& stream) noexcept { // exponent GEODE_UNWRAP_INTO(p, stream.peek()); if (p == 'e' || p == 'E') { + isFloating = true; GEODE_UNWRAP(addToBuffer()); GEODE_UNWRAP_INTO(p, stream.peek()); @@ -231,17 +236,28 @@ Result parseNumber(StringStream& stream) noexcept { GEODE_UNWRAP(takeDigits()); } } + auto const fromCharsHelper = [&]() -> Result { + T value; + if (auto result = std::from_chars(buffer.data(), buffer.data() + buffer.size(), value); + result.ec != std::errc()) { + return Err("failed to parse number"); + } + return Ok(std::make_unique(Type::Number, value)); + }; + if (isFloating) { #ifndef __cpp_lib_to_chars - // FIXME: std::stod is locale specific, might break on some machines - return Ok(std::make_unique(Type::Number, std::stod(buffer))); + // FIXME: std::stod is locale specific, might break on some machines + return Ok(std::make_unique(Type::Number, std::stod(buffer))); #else - double value; - if (auto result = std::from_chars(buffer.data(), buffer.data() + buffer.size(), value); - result.ec != std::errc()) { - return Err("failed to parse number"); - } - return Ok(std::make_unique(Type::Number, value)); + return fromCharsHelper.operator()(); #endif + } + else if (isNegative) { + return fromCharsHelper.operator()(); + } + else { + return fromCharsHelper.operator()(); + } } // parses a json element with optional whitespace around it diff --git a/src/value.cpp b/src/value.cpp index 0d6777a..41f1762 100644 --- a/src/value.cpp +++ b/src/value.cpp @@ -19,6 +19,14 @@ Value::Value(double value) { m_impl = std::make_unique(Type::Number, value); } +Value::Value(intmax_t value) { + m_impl = std::make_unique(Type::Number, value); +} + +Value::Value(uintmax_t value) { + m_impl = std::make_unique(Type::Number, value); +} + Value::Value(bool value) { m_impl = std::make_unique(Type::Bool, value); } @@ -61,11 +69,11 @@ static Value& asNotConst(Value const& value) { return const_cast(value); } -Result Value::get(std::string_view key) { +Result Value::get(std::string_view key) { return std::as_const(*this).get(key).map(asNotConst); } -Result Value::get(std::string_view key) const { +Result Value::get(std::string_view key) const { if (this->type() != Type::Object) { return Err("not an object"); } @@ -78,11 +86,11 @@ Result Value::get(std::string_view key) const { return Err("key not found"); } -Result Value::get(size_t index) { +Result Value::get(size_t index) { return std::as_const(*this).get(index).map(asNotConst); } -Result Value::get(size_t index) const { +Result Value::get(size_t index) const { if (this->type() != Type::Array) { return Err("not an array"); } @@ -126,6 +134,48 @@ Value const& Value::operator[](size_t index) const { }); } +void Value::set(std::string_view key, Value value) { + if (this->type() != Type::Object) { + return; + } + (void)this->get(key) + .inspect([&value](Value& v) { + v = std::move(value); + }) + .inspectErr([&](auto&&) { + auto& arr = m_impl->asArray(); + arr.emplace_back(std::move(value)); + arr.back().m_impl->setKey(std::string(key)); + }); +} + +bool Value::erase(std::string_view key) { + if (this->type() != Type::Object) { + return false; + } + auto& arr = m_impl->asArray(); + for (auto it = arr.begin(); it != arr.end(); ++it) { + if (it->m_impl->key().value() == key) { + arr.erase(it); + return true; + } + } + return false; +} + +bool Value::contains(std::string_view key) const { + if (this->type() != Type::Object) { + return false; + } + auto const& arr = m_impl->asArray(); + for (auto const& value : arr) { + if (value.m_impl->key().value() == key) { + return true; + } + } + return false; +} + Type Value::type() const { return m_impl->type(); } @@ -161,3 +211,38 @@ std::vector::const_iterator Value::end() const { std::optional Value::getKey() const { return m_impl->key(); } + +Result Value::asBool() const { + if (this->type() != Type::Bool) { + return Err("not a bool"); + } + return Ok(m_impl->asBool()); +} + +Result Value::asString() const { + if (this->type() != Type::String) { + return Err("not a string"); + } + return Ok(m_impl->asString()); +} + +Result Value::asInt() const { + if (this->type() != Type::Number) { + return Err("not a number"); + } + return Ok(m_impl->asNumber()); +} + +Result Value::asUInt() const { + if (this->type() != Type::Number) { + return Err("not a number"); + } + return Ok(m_impl->asNumber()); +} + +Result Value::asDouble() const { + if (this->type() != Type::Number) { + return Err("not a number"); + } + return Ok(m_impl->asNumber()); +} diff --git a/test/test.cpp b/test/test.cpp index e9f503b..bf1cfa2 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -5,13 +5,27 @@ using namespace geode; Result fancyMain(int argc, char const* argv[]) { - auto const json = GEODE_UNWRAP(matjson::parse("{\"hi\": 123}")); + auto const json = GEODE_UNWRAP(matjson::parse("{\"hi\": 123.51}")); auto& x = json["wow"]["crazy"]; matjson::Value test = 123.1; test = std::move(x); - fmt::println("{}", GEODE_UNWRAP(json["hello"]["world"]["lol"].dump())); - fmt::println("{}", GEODE_UNWRAP(json.dump())); + fmt::println("{}", json["hello"]["world"]["lol"]); + fmt::println("{}", json); + fmt::println("{}", GEODE_UNWRAP(json["hi"].asInt())); + fmt::println("{}", GEODE_UNWRAP(json["hi"].asDouble())); + + auto mjson = json; + mjson.set("hi", 123.5); + mjson.set("oworld", "uwu"); + fmt::println("{}", mjson); + mjson = 123; + + mjson = GEODE_UNWRAP(matjson::parse("{\"big number\": 123123123123123123}")); + fmt::println("{}", mjson); + fmt::println("{}", GEODE_UNWRAP(mjson["big number"].asInt())); + fmt::println("{}", GEODE_UNWRAP(mjson["big number"].asUInt())); + fmt::println("{}", GEODE_UNWRAP(mjson["big number"].asDouble())); if (argc > 1) { std::fstream file(argv[1]);