Skip to content

Commit

Permalink
support int and uint max
Browse files Browse the repository at this point in the history
  • Loading branch information
matcool committed Oct 21, 2024
1 parent 84780dd commit 2e1df21
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 47 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 29 additions & 9 deletions include/matjson3.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ namespace matjson {
class ValueIterator;

using Array = std::vector<Value>;
// 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;
Expand All @@ -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 <class T>
requires std::is_integral_v<T> && std::is_signed_v<T>
Value(T value) : Value(static_cast<std::intmax_t>(value)) {}

template <class T>
requires std::is_integral_v<T> && std::is_unsigned_v<T>
Value(T value) : Value(static_cast<std::uintmax_t>(value)) {}

template <class T>
// Prevents implicit conversion from pointer to bool
Expand All @@ -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<Value&, PenisError> get(std::string_view key);
geode::Result<Value&, GenericError> 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<Value const&, PenisError> get(std::string_view key) const;
geode::Result<Value const&, GenericError> 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<Value&, PenisError> get(size_t index);
geode::Result<Value&, GenericError> 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<Value const&, PenisError> get(size_t index) const;
geode::Result<Value const&, GenericError> get(size_t index) const;

/// Returns the value associated with the given key
/// @param key Object key
Expand Down Expand Up @@ -136,7 +144,7 @@ namespace matjson {
bool operator<(Value const&) const;
bool operator>(Value const&) const;

geode::Result<std::string, PenisError> dump(int indentationSize = 4) const;
geode::Result<std::string, GenericError> dump(int indentationSize = 4) const;

Type type() const;

Expand Down Expand Up @@ -170,6 +178,12 @@ namespace matjson {
return this->type() == Type::Object;
}

geode::Result<bool, GenericError> asBool() const;
geode::Result<std::string, GenericError> asString() const;
geode::Result<std::intmax_t, GenericError> asInt() const;
geode::Result<std::uintmax_t, GenericError> asUInt() const;
geode::Result<double, GenericError> asDouble() const;

std::optional<std::string> getKey() const;
};

Expand Down Expand Up @@ -203,6 +217,12 @@ namespace matjson {
return std::forward<T>(value);
}
}

// For fmtlib, lol
inline std::string format_as(matjson::Value const& value) {
// let the exception through
return value.dump().unwrap();
}
}

// allow destructuring
Expand Down
38 changes: 30 additions & 8 deletions src/dump.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
#include "impl.hpp"

#include <array>
#include <charconv>
#include <cmath>
#include <cstdio>
#include <matjson3.hpp>
#include <string>

// 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;
Expand Down Expand Up @@ -36,21 +42,37 @@ void dumpJsonString(std::string_view str, std::string& out) {
out.push_back('"');
}

Result<void, PenisError> dumpJsonNumber(ValueImpl const& impl, std::string& out) {
if (impl.isInteger()) {
out += std::to_string(impl.asInt());
Result<void, GenericError> dumpJsonNumber(ValueImpl const& impl, std::string& out) {
if (impl.isInt()) {
out += std::to_string(impl.asNumber<intmax_t>());
}
else if (impl.isUInt()) {
out += std::to_string(impl.asNumber<uintmax_t>());
}
else {
auto number = impl.asDouble();
auto number = impl.asNumber<double>();
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<char, dragonbox::DtoaMinBufferLength> buffer;
auto* end = dragonbox::Dtoa(buffer.data(), number);
out += std::string_view(buffer.data(), end - buffer.data());
#else
std::array<char, 32> 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<void, PenisError> dumpImpl(Value const& value, std::string& out, int indentation, int depth) {
Result<void, GenericError> dumpImpl(Value const& value, std::string& out, int indentation, int depth) {
auto& impl = ValueImpl::fromValue(value);
switch (value.type()) {
case Type::Null: {
Expand Down Expand Up @@ -121,7 +143,7 @@ Result<void, PenisError> dumpImpl(Value const& value, std::string& out, int inde
return Ok();
}

Result<std::string, PenisError> Value::dump(int indentationSize) const {
Result<std::string, GenericError> Value::dump(int indentationSize) const {
std::string out;
GEODE_UNWRAP(dumpImpl(*this, out, indentationSize, 0));
return Ok(out);
Expand Down
35 changes: 22 additions & 13 deletions src/impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
#include <matjson3.hpp>
#include <variant>

using std::intmax_t;
using std::size_t;
using std::uintmax_t;

class matjson::ValueImpl {
Type m_type;
std::optional<std::string> m_key;
std::variant<std::monostate, std::string, double, std::int64_t, bool, Array> m_value;
std::variant<std::monostate, std::string, double, intmax_t, uintmax_t, bool, Array> m_value;

public:
template <class T>
Expand Down Expand Up @@ -43,18 +47,6 @@ class matjson::ValueImpl {
return std::get<std::string>(m_value);
}

double asDouble() const {
return std::get<double>(m_value);
}

std::int64_t asInt() const {
return std::get<std::int64_t>(m_value);
}

bool isInteger() const {
return std::holds_alternative<std::int64_t>(m_value);
}

Array& asArray() {
return std::get<Array>(m_value);
}
Expand All @@ -66,6 +58,23 @@ class matjson::ValueImpl {
auto const& getVariant() {
return m_value;
}

bool isInt() const {
return std::holds_alternative<intmax_t>(m_value);
}

bool isUInt() const {
return std::holds_alternative<uintmax_t>(m_value);
}

template <class NumberType>
NumberType asNumber() const {
if (std::holds_alternative<intmax_t>(m_value))
return static_cast<NumberType>(std::get<intmax_t>(m_value));
else if (std::holds_alternative<uintmax_t>(m_value))
return static_cast<NumberType>(std::get<uintmax_t>(m_value));
else return static_cast<NumberType>(std::get<double>(m_value));
}
};

using ValuePtr = std::unique_ptr<matjson::ValueImpl>;
Expand Down
32 changes: 24 additions & 8 deletions src/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,16 @@ Result<std::string, ParseError> parseString(StringStream& stream) noexcept {

Result<ValuePtr, ParseError> parseNumber(StringStream& stream) noexcept {
std::string buffer;
bool isFloating = false;
bool isNegative = false;
auto const addToBuffer = [&]() -> Result<void, ParseError> {
GEODE_UNWRAP_INTO(char c, stream.take());
buffer.push_back(c);
return Ok();
};
GEODE_UNWRAP_INTO(char p, stream.peek());
if (p == '-') {
isNegative = true;
GEODE_UNWRAP(addToBuffer());
}
auto const takeDigits = [&]() -> Result<void, ParseError> {
Expand Down Expand Up @@ -213,6 +216,7 @@ Result<ValuePtr, ParseError> parseNumber(StringStream& stream) noexcept {
// fraction
GEODE_UNWRAP_INTO(p, stream.peek());
if (p == '.') {
isFloating = true;
GEODE_UNWRAP(addToBuffer());

GEODE_UNWRAP(takeDigits());
Expand All @@ -222,6 +226,7 @@ Result<ValuePtr, ParseError> 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());
Expand All @@ -231,17 +236,28 @@ Result<ValuePtr, ParseError> parseNumber(StringStream& stream) noexcept {
GEODE_UNWRAP(takeDigits());
}
}
auto const fromCharsHelper = [&]<class T>() -> Result<ValuePtr, ParseError> {
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<ValueImpl>(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<ValueImpl>(Type::Number, std::stod(buffer)));
// FIXME: std::stod is locale specific, might break on some machines
return Ok(std::make_unique<ValueImpl>(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<ValueImpl>(Type::Number, value));
return fromCharsHelper.operator()<double>();
#endif
}
else if (isNegative) {
return fromCharsHelper.operator()<intmax_t>();
}
else {
return fromCharsHelper.operator()<uintmax_t>();
}
}

// parses a json element with optional whitespace around it
Expand Down
Loading

0 comments on commit 2e1df21

Please sign in to comment.