Files
AntlrLab/src/json.hpp
T
alejo ba5971a788
build / linux-clang (push) Canceled after 0s
build / linux-gcc (push) Canceled after 0s
build / macos-clang (push) Canceled after 0s
build / windows-mingw (push) Canceled after 0s
build / windows-msvc (push) Canceled after 0s
Add project files
2026-10-07 00:25:24 -03:00

278 lines
7.8 KiB
C++

// A small, strict JSON parser. It only has to read the request bodies that the
// web UI sends, but it validates everything (depth limit, escapes, UTF-16
// surrogates) because those bodies arrive over a socket.
#pragma once
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
namespace lab {
struct Json {
enum class Type { Null, Bool, Number, String, Array, Object };
Type type = Type::Null;
bool b = false;
double n = 0;
std::string s;
std::vector<Json> a;
std::vector<std::pair<std::string, Json>> o;
const Json* find(const std::string& key) const {
if (type != Type::Object) return nullptr;
for (const auto& kv : o) {
if (kv.first == key) return &kv.second;
}
return nullptr;
}
std::string str(const std::string& key, const std::string& def = "") const {
const Json* v = find(key);
return (v && v->type == Type::String) ? v->s : def;
}
bool boolean(const std::string& key, bool def = false) const {
const Json* v = find(key);
return (v && v->type == Type::Bool) ? v->b : def;
}
double number(const std::string& key, double def = 0) const {
const Json* v = find(key);
return (v && v->type == Type::Number) ? v->n : def;
}
const std::vector<Json>* array(const std::string& key) const {
const Json* v = find(key);
return (v && v->type == Type::Array) ? &v->a : nullptr;
}
};
namespace detail {
class JsonParser {
public:
explicit JsonParser(const std::string& text) : t_(text) {}
bool parse(Json& out, std::string& error) {
skipWs();
if (!value(out, 0)) {
error = err_.empty() ? "invalid JSON" : err_;
return false;
}
skipWs();
if (pos_ != t_.size()) {
error = "unexpected text after the JSON value";
return false;
}
return true;
}
private:
const std::string& t_;
size_t pos_ = 0;
std::string err_;
bool fail(const char* msg) {
if (err_.empty()) err_ = std::string(msg) + " at offset " + std::to_string(pos_);
return false;
}
void skipWs() {
while (pos_ < t_.size() && (t_[pos_] == ' ' || t_[pos_] == '\t' || t_[pos_] == '\n' || t_[pos_] == '\r')) ++pos_;
}
bool literal(const char* word) {
size_t i = 0;
while (word[i]) {
if (pos_ + i >= t_.size() || t_[pos_ + i] != word[i]) return false;
++i;
}
pos_ += i;
return true;
}
static void appendUtf8(std::string& out, uint32_t cp) {
if (cp < 0x80) {
out += static_cast<char>(cp);
} else if (cp < 0x800) {
out += static_cast<char>(0xC0 | (cp >> 6));
out += static_cast<char>(0x80 | (cp & 0x3F));
} else if (cp < 0x10000) {
out += static_cast<char>(0xE0 | (cp >> 12));
out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
out += static_cast<char>(0x80 | (cp & 0x3F));
} else {
out += static_cast<char>(0xF0 | (cp >> 18));
out += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
out += static_cast<char>(0x80 | (cp & 0x3F));
}
}
bool hex4(uint32_t& v) {
if (pos_ + 4 > t_.size()) return fail("truncated \\u escape");
v = 0;
for (int i = 0; i < 4; ++i) {
const char c = t_[pos_++];
v <<= 4;
if (c >= '0' && c <= '9') v |= static_cast<uint32_t>(c - '0');
else if (c >= 'a' && c <= 'f') v |= static_cast<uint32_t>(c - 'a' + 10);
else if (c >= 'A' && c <= 'F') v |= static_cast<uint32_t>(c - 'A' + 10);
else return fail("bad \\u escape");
}
return true;
}
bool string(std::string& out) {
if (pos_ >= t_.size() || t_[pos_] != '"') return fail("expected a string");
++pos_;
out.clear();
while (pos_ < t_.size()) {
const unsigned char c = static_cast<unsigned char>(t_[pos_++]);
if (c == '"') return true;
if (c < 0x20) return fail("control character in string");
if (c != '\\') {
out += static_cast<char>(c);
continue;
}
if (pos_ >= t_.size()) break;
const char e = t_[pos_++];
switch (e) {
case '"': out += '"'; break;
case '\\': out += '\\'; break;
case '/': out += '/'; break;
case 'b': out += '\b'; break;
case 'f': out += '\f'; break;
case 'n': out += '\n'; break;
case 'r': out += '\r'; break;
case 't': out += '\t'; break;
case 'u': {
uint32_t cp = 0;
if (!hex4(cp)) return false;
if (cp >= 0xD800 && cp <= 0xDBFF) {
uint32_t lo = 0;
if (pos_ + 1 < t_.size() && t_[pos_] == '\\' && t_[pos_ + 1] == 'u') {
pos_ += 2;
if (!hex4(lo)) return false;
}
if (lo >= 0xDC00 && lo <= 0xDFFF) cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
else cp = 0xFFFD;
} else if (cp >= 0xDC00 && cp <= 0xDFFF) {
cp = 0xFFFD;
}
appendUtf8(out, cp);
break;
}
default: return fail("bad escape");
}
}
return fail("unterminated string");
}
bool number(double& out) {
const size_t start = pos_;
if (pos_ < t_.size() && t_[pos_] == '-') ++pos_;
bool digits = false;
while (pos_ < t_.size() && ((t_[pos_] >= '0' && t_[pos_] <= '9') || t_[pos_] == '.' || t_[pos_] == 'e' ||
t_[pos_] == 'E' || t_[pos_] == '+' || t_[pos_] == '-')) {
if (t_[pos_] >= '0' && t_[pos_] <= '9') digits = true;
++pos_;
}
if (!digits) return fail("bad number");
try {
out = std::stod(t_.substr(start, pos_ - start));
} catch (...) {
return fail("bad number");
}
return true;
}
bool value(Json& out, int depth) {
if (depth > 32) return fail("nesting too deep");
if (pos_ >= t_.size()) return fail("unexpected end");
const char c = t_[pos_];
if (c == '{') {
++pos_;
out.type = Json::Type::Object;
skipWs();
if (pos_ < t_.size() && t_[pos_] == '}') {
++pos_;
return true;
}
for (;;) {
skipWs();
std::string key;
if (!string(key)) return false;
skipWs();
if (pos_ >= t_.size() || t_[pos_] != ':') return fail("expected ':'");
++pos_;
skipWs();
Json v;
if (!value(v, depth + 1)) return false;
out.o.emplace_back(std::move(key), std::move(v));
skipWs();
if (pos_ < t_.size() && t_[pos_] == ',') {
++pos_;
continue;
}
if (pos_ < t_.size() && t_[pos_] == '}') {
++pos_;
return true;
}
return fail("expected ',' or '}'");
}
}
if (c == '[') {
++pos_;
out.type = Json::Type::Array;
skipWs();
if (pos_ < t_.size() && t_[pos_] == ']') {
++pos_;
return true;
}
for (;;) {
skipWs();
Json v;
if (!value(v, depth + 1)) return false;
out.a.push_back(std::move(v));
skipWs();
if (pos_ < t_.size() && t_[pos_] == ',') {
++pos_;
continue;
}
if (pos_ < t_.size() && t_[pos_] == ']') {
++pos_;
return true;
}
return fail("expected ',' or ']'");
}
}
if (c == '"') {
out.type = Json::Type::String;
return string(out.s);
}
if (c == 't') {
out.type = Json::Type::Bool;
out.b = true;
return literal("true") || fail("bad literal");
}
if (c == 'f') {
out.type = Json::Type::Bool;
out.b = false;
return literal("false") || fail("bad literal");
}
if (c == 'n') {
out.type = Json::Type::Null;
return literal("null") || fail("bad literal");
}
out.type = Json::Type::Number;
return number(out.n);
}
};
} // namespace detail
inline bool parseJson(const std::string& text, Json& out, std::string& error) {
out = Json();
detail::JsonParser p(text);
return p.parse(out, error);
}
} // namespace lab