From 09206ddc5c2681c3e5cad503920bb5e8497f2773 Mon Sep 17 00:00:00 2001 From: Robin Linden Date: Tue, 14 Jan 2025 02:28:04 +0100 Subject: [PATCH] json: Support parsing numbers --- json/json.h | 98 ++++++++++++++++++++++++++++++++++++++++++++++ json/json_test.cpp | 18 +++++++++ 2 files changed, 116 insertions(+) diff --git a/json/json.h b/json/json.h index 2508e61a..e526deac 100644 --- a/json/json.h +++ b/json/json.h @@ -114,6 +114,10 @@ class Parser { return std::nullopt; } + if (*c == '-' || (*c >= '0' && *c <= '9')) { + return parse_number(); + } + switch (*c) { case '"': return parse_string(); @@ -132,6 +136,100 @@ class Parser { } } + constexpr std::optional parse_number() { + std::string number; + if (auto c = peek(); c == '-') { + number.push_back('-'); + std::ignore = consume(); + } + + if (auto c = peek(); c == '0') { + number.push_back('0'); + std::ignore = consume(); + } else if (c >= '1' && c <= '9') { + number.push_back(*c); + std::ignore = consume(); + + for (c = peek(); c && *c >= '0' && *c <= '9'; c = peek()) { + number.push_back(*c); + std::ignore = consume(); + } + } else { + return std::nullopt; + } + + bool is_floating_point = false; + if (peek() == '.') { + number.push_back('.'); + std::ignore = consume(); + is_floating_point = true; + + auto c = peek(); + if (!c || *c < '0' || *c > '9') { + return std::nullopt; + } + + number.push_back(*c); + std::ignore = consume(); + + while ((c = peek())) { + if (*c >= '0' && *c <= '9') { + number.push_back(*c); + std::ignore = consume(); + continue; + } + + break; + } + } + + if (auto c = peek(); c == 'e' || c == 'E') { + number.push_back(*c); + std::ignore = consume(); + is_floating_point = true; + + if (c = peek(); c == '+' || c == '-') { + number.push_back(*c); + std::ignore = consume(); + } + + if (c = peek(); !c || *c < '0' || *c > '9') { + return std::nullopt; + } + + number.push_back(*c); + std::ignore = consume(); + + while ((c = peek())) { + if (*c >= '0' && *c <= '9') { + number.push_back(*c); + std::ignore = consume(); + continue; + } + + break; + } + } + + if (!is_floating_point) { + std::int64_t value{}; + if (auto [p, ec] = std::from_chars(number.data(), number.data() + number.size(), value); + ec != std::errc{} || p != number.data() + number.size()) { + return std::nullopt; + } + + return Value{value}; + } + + double value{}; + if (auto [p, ec] = std::from_chars(number.data(), number.data() + number.size(), value); + ec != std::errc{} || p != number.data() + number.size()) { + return std::nullopt; + } + + return Value{value}; + } + // NOLINTNEXTLINE(misc-no-recursion) constexpr std::optional parse_object() { std::ignore = consume(); // '{' diff --git a/json/json_test.cpp b/json/json_test.cpp index 485bde3e..7d571ac4 100644 --- a/json/json_test.cpp +++ b/json/json_test.cpp @@ -107,5 +107,23 @@ int main() { a.expect_eq(json::parse(R"({"key":true})"), Value{json::Object{{{"key", Value{true}}}}}); }); + s.add_test("numbers", [](etest::IActions &a) { + a.expect_eq(json::parse("0"), Value{0}); + a.expect_eq(json::parse("1"), Value{1}); + a.expect_eq(json::parse("123"), Value{123}); + a.expect_eq(json::parse("123.456"), Value{123.456}); + a.expect_eq(json::parse("-0"), Value{-0}); + a.expect_eq(json::parse("-1"), Value{-1}); + a.expect_eq(json::parse("-123"), Value{-123}); + a.expect_eq(json::parse("-123.456"), Value{-123.456}); + a.expect_eq(json::parse("0.123"), Value{0.123}); + a.expect_eq(json::parse("0.123e4"), Value{0.123e4}); + a.expect_eq(json::parse("0.123e-4"), Value{0.123e-4}); + a.expect_eq(json::parse("0.123e+4"), Value{0.123e+4}); + + a.expect_eq(json::parse("0.123e456"), std::nullopt); // out-of-range + a.expect_eq(json::parse("123."), std::nullopt); + }); + return s.run(); }