146 lines
5.2 KiB
C++
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
|