sots-engine/src/mars/parse/value.cpp

95 lines
3.1 KiB
C++

#include "value.h"
#include <cerrno>
#include <cstdlib>
#include <string>
namespace mars::parse {
namespace {
bool is_digit(unsigned char c) { return c >= '0' && c <= '9'; }
// Consumes the optional sign, returns the rest.
std::string_view strip_sign(std::string_view s) {
if (!s.empty() && (s[0] == '+' || s[0] == '-')) s.remove_prefix(1);
return s;
}
bool all_digits(std::string_view s) {
if (s.empty()) return false;
for (unsigned char c : s)
if (!is_digit(c)) return false;
return true;
}
} // namespace
unsigned char ascii_lower(unsigned char c) {
return (c >= 'A' && c <= 'Z') ? static_cast<unsigned char>(c + ('a' - 'A')) : c;
}
bool iequals(std::string_view a, std::string_view b) {
if (a.size() != b.size()) return false;
for (std::size_t i = 0; i < a.size(); ++i)
if (ascii_lower(static_cast<unsigned char>(a[i])) !=
ascii_lower(static_cast<unsigned char>(b[i])))
return false;
return true;
}
ScalarKind classify(std::string_view tok) {
if (tok.empty()) return ScalarKind::Text;
if (iequals(tok, "true") || iequals(tok, "false")) return ScalarKind::Bool;
std::string_view body = strip_sign(tok);
if (all_digits(body)) return ScalarKind::Int;
// mantissa: d+.d* | .d+ | d+
std::size_t i = 0;
std::size_t int_digits = 0;
while (i < body.size() && is_digit(static_cast<unsigned char>(body[i]))) { ++i; ++int_digits; }
std::size_t frac_digits = 0;
if (i < body.size() && body[i] == '.') {
++i;
while (i < body.size() && is_digit(static_cast<unsigned char>(body[i]))) { ++i; ++frac_digits; }
}
if (int_digits == 0 && frac_digits == 0) return ScalarKind::Text; // ".", "-", "e5"
// optional exponent (a dotless mantissa with an exponent, "3e5", is a float)
if (i < body.size() && (body[i] == 'e' || body[i] == 'E')) {
++i;
if (i < body.size() && (body[i] == '+' || body[i] == '-')) ++i;
std::size_t exp_digits = 0;
while (i < body.size() && is_digit(static_cast<unsigned char>(body[i]))) { ++i; ++exp_digits; }
if (exp_digits == 0) return ScalarKind::Text;
}
return i == body.size() ? ScalarKind::Float : ScalarKind::Text;
}
std::optional<std::int64_t> as_int(std::string_view tok) {
if (classify(tok) != ScalarKind::Int) return std::nullopt;
std::string buf(tok);
errno = 0;
char* end = nullptr;
long long v = std::strtoll(buf.c_str(), &end, 10);
if (errno == ERANGE || end != buf.c_str() + buf.size()) return std::nullopt;
return static_cast<std::int64_t>(v);
}
std::optional<double> as_double(std::string_view tok) {
ScalarKind k = classify(tok);
if (k != ScalarKind::Int && k != ScalarKind::Float) return std::nullopt;
std::string buf(tok);
char* end = nullptr;
double v = std::strtod(buf.c_str(), &end);
if (end != buf.c_str() + buf.size()) return std::nullopt;
return v;
}
std::optional<bool> as_bool(std::string_view tok) {
if (iequals(tok, "true")) return true;
if (iequals(tok, "false")) return false;
return std::nullopt;
}
} // namespace mars::parse