Skip to content

Commit

Permalink
json: Support parsing numbers
Browse files Browse the repository at this point in the history
  • Loading branch information
robinlinden committed Jan 14, 2025
1 parent 3757153 commit 09206dd
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 0 deletions.
98 changes: 98 additions & 0 deletions json/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ class Parser {
return std::nullopt;
}

if (*c == '-' || (*c >= '0' && *c <= '9')) {
return parse_number();
}

switch (*c) {
case '"':
return parse_string();
Expand All @@ -132,6 +136,100 @@ class Parser {
}
}

constexpr std::optional<Value> 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<Value> parse_object() {
std::ignore = consume(); // '{'
Expand Down
18 changes: 18 additions & 0 deletions json/json_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

0 comments on commit 09206dd

Please sign in to comment.