95 lines
3.1 KiB
C++
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
|