sots-engine/tests/game_sim/mini_json.h

146 lines
5.2 KiB
C++

// Tiny dependency-free JSON reader for the real-save smoke test. Not for production use:
// no error recovery, accepts a superset of JSON, numbers parsed as double.
#pragma once
#include <cctype>
#include <cstdlib>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace minijson {
struct Value {
enum Kind { Null, Bool, Number, String, Array, Object } kind = Null;
bool b = false;
double num = 0;
std::string str;
std::vector<Value> arr;
std::map<std::string, Value> obj;
const Value* get(const std::string& key) const {
if (kind != Object) return nullptr;
auto it = obj.find(key);
return it == obj.end() ? nullptr : &it->second;
}
double number(double dflt = 0) const { return kind == Number ? num : (kind == Bool ? (b ? 1 : 0) : dflt); }
bool boolean(bool dflt = false) const { return kind == Bool ? b : (kind == Number ? num != 0 : dflt); }
const std::string& string() const { return str; }
bool isArray() const { return kind == Array; }
bool isObject() const { return kind == Object; }
};
class Parser {
public:
explicit Parser(const std::string& s) : s_(s) {}
bool parse(Value& out) {
skip();
if (!value(out)) return false;
skip();
return pos_ == s_.size();
}
private:
const std::string& s_;
std::size_t pos_ = 0;
void skip() { while (pos_ < s_.size() && std::isspace(static_cast<unsigned char>(s_[pos_]))) ++pos_; }
bool eat(char c) { if (pos_ < s_.size() && s_[pos_] == c) { ++pos_; return true; } return false; }
bool value(Value& v) {
if (pos_ >= s_.size()) return false;
const char c = s_[pos_];
if (c == '{') return object(v);
if (c == '[') return array(v);
if (c == '"') { v.kind = Value::String; return stringLit(v.str); }
if (s_.compare(pos_, 4, "true") == 0) { pos_ += 4; v.kind = Value::Bool; v.b = true; return true; }
if (s_.compare(pos_, 5, "false") == 0) { pos_ += 5; v.kind = Value::Bool; v.b = false; return true; }
if (s_.compare(pos_, 4, "null") == 0) { pos_ += 4; v.kind = Value::Null; return true; }
if (s_.compare(pos_, 3, "NaN") == 0) { pos_ += 3; v.kind = Value::Number; v.num = 0; return true; }
if (s_.compare(pos_, 8, "Infinity") == 0) { pos_ += 8; v.kind = Value::Number; v.num = 0; return true; }
if (s_.compare(pos_, 9, "-Infinity") == 0) { pos_ += 9; v.kind = Value::Number; v.num = 0; return true; }
return number(v);
}
bool number(Value& v) {
const char* begin = s_.c_str() + pos_;
char* end = nullptr;
const double d = std::strtod(begin, &end);
if (end == begin) return false;
pos_ += static_cast<std::size_t>(end - begin);
v.kind = Value::Number;
v.num = d;
return true;
}
bool stringLit(std::string& out) {
if (!eat('"')) return false;
out.clear();
while (pos_ < s_.size()) {
const char c = s_[pos_++];
if (c == '"') return true;
if (c != '\\') { out.push_back(c); continue; }
if (pos_ >= s_.size()) return false;
const char e = s_[pos_++];
switch (e) {
case 'n': out.push_back('\n'); break;
case 't': out.push_back('\t'); break;
case 'r': out.push_back('\r'); break;
case 'b': out.push_back('\b'); break;
case 'f': out.push_back('\f'); break;
case 'u': {
if (pos_ + 4 > s_.size()) return false;
const unsigned long cp = std::strtoul(s_.substr(pos_, 4).c_str(), nullptr, 16);
pos_ += 4;
if (cp < 0x80) out.push_back(static_cast<char>(cp));
else if (cp < 0x800) { out.push_back(static_cast<char>(0xC0 | (cp >> 6))); out.push_back(static_cast<char>(0x80 | (cp & 0x3F))); }
else { out.push_back(static_cast<char>(0xE0 | (cp >> 12))); out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F))); out.push_back(static_cast<char>(0x80 | (cp & 0x3F))); }
break;
}
default: out.push_back(e); break;
}
}
return false;
}
bool array(Value& v) {
if (!eat('[')) return false;
v.kind = Value::Array;
skip();
if (eat(']')) return true;
for (;;) {
Value item;
skip();
if (!value(item)) return false;
v.arr.push_back(std::move(item));
skip();
if (eat(',')) continue;
return eat(']');
}
}
bool object(Value& v) {
if (!eat('{')) return false;
v.kind = Value::Object;
skip();
if (eat('}')) return true;
for (;;) {
skip();
std::string key;
if (!stringLit(key)) return false;
skip();
if (!eat(':')) return false;
skip();
Value item;
if (!value(item)) return false;
v.obj[key] = std::move(item);
skip();
if (eat(',')) continue;
return eat('}');
}
}
};
} // namespace minijson