// 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 #include #include #include #include #include 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 arr; std::map 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(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(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(cp)); else if (cp < 0x800) { out.push_back(static_cast(0xC0 | (cp >> 6))); out.push_back(static_cast(0x80 | (cp & 0x3F))); } else { out.push_back(static_cast(0xE0 | (cp >> 12))); out.push_back(static_cast(0x80 | ((cp >> 6) & 0x3F))); out.push_back(static_cast(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