236 lines
8.2 KiB
C++
236 lines
8.2 KiB
C++
// A minimal JSON reader for the test tools (reads the owner's designs JSON).
|
|
// Header-only, no dependencies, no exceptions: parse() returns false and an
|
|
// error message on malformed input. Numbers are kept as double plus the text.
|
|
#pragma once
|
|
|
|
#include <cstdlib>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
namespace json_min {
|
|
|
|
struct Value {
|
|
enum class Kind { Null, Bool, Number, String, Array, Object };
|
|
Kind kind = Kind::Null;
|
|
bool b = false;
|
|
double num = 0;
|
|
std::string text; // string value or number text
|
|
std::vector<Value> items; // array
|
|
std::vector<std::pair<std::string, Value>> members; // object, file order
|
|
|
|
bool is_null() const { return kind == Kind::Null; }
|
|
bool is_string() const { return kind == Kind::String; }
|
|
bool is_number() const { return kind == Kind::Number; }
|
|
bool is_array() const { return kind == Kind::Array; }
|
|
bool is_object() const { return kind == Kind::Object; }
|
|
bool is_bool() const { return kind == Kind::Bool; }
|
|
|
|
const Value* get(std::string_view key) const {
|
|
for (const auto& m : members)
|
|
if (m.first == key) return &m.second;
|
|
return nullptr;
|
|
}
|
|
const Value& operator[](std::string_view key) const {
|
|
static const Value null;
|
|
const Value* v = get(key);
|
|
return v ? *v : null;
|
|
}
|
|
const std::string& str() const { return text; }
|
|
int as_int() const { return static_cast<int>(num); }
|
|
bool as_bool() const { return kind == Kind::Bool ? b : (kind == Kind::Number ? num != 0 : false); }
|
|
};
|
|
|
|
class Parser {
|
|
public:
|
|
explicit Parser(std::string_view s) : s_(s) {}
|
|
|
|
bool parse(Value& out, std::string& err) {
|
|
skip();
|
|
if (!value(out, err)) return false;
|
|
skip();
|
|
if (pos_ != s_.size()) return fail(err, "trailing characters");
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
std::string_view s_;
|
|
std::size_t pos_ = 0;
|
|
|
|
bool fail(std::string& err, const char* what) {
|
|
err = std::string(what) + " at offset " + std::to_string(pos_);
|
|
return false;
|
|
}
|
|
void skip() {
|
|
while (pos_ < s_.size() && (s_[pos_] == ' ' || s_[pos_] == '\n' || s_[pos_] == '\r' || s_[pos_] == '\t')) ++pos_;
|
|
}
|
|
bool literal(const char* lit) {
|
|
std::string_view l(lit);
|
|
if (s_.substr(pos_, l.size()) != l) return false;
|
|
pos_ += l.size();
|
|
return true;
|
|
}
|
|
static void put_utf8(std::string& out, unsigned 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(unsigned& cp) {
|
|
if (pos_ + 4 > s_.size()) return false;
|
|
cp = 0;
|
|
for (int i = 0; i < 4; ++i) {
|
|
char c = s_[pos_++];
|
|
cp <<= 4;
|
|
if (c >= '0' && c <= '9') cp |= static_cast<unsigned>(c - '0');
|
|
else if (c >= 'a' && c <= 'f') cp |= static_cast<unsigned>(c - 'a' + 10);
|
|
else if (c >= 'A' && c <= 'F') cp |= static_cast<unsigned>(c - 'A' + 10);
|
|
else return false;
|
|
}
|
|
return true;
|
|
}
|
|
bool string(std::string& out, std::string& err) {
|
|
if (pos_ >= s_.size() || s_[pos_] != '"') return fail(err, "expected string");
|
|
++pos_;
|
|
while (pos_ < s_.size()) {
|
|
char c = s_[pos_++];
|
|
if (c == '"') return true;
|
|
if (c != '\\') {
|
|
out += c;
|
|
continue;
|
|
}
|
|
if (pos_ >= s_.size()) break;
|
|
char e = s_[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': {
|
|
unsigned cp;
|
|
if (!hex4(cp)) return fail(err, "bad \\u escape");
|
|
if (cp >= 0xD800 && cp < 0xDC00 && literal("\\u")) {
|
|
unsigned lo;
|
|
if (!hex4(lo)) return fail(err, "bad surrogate");
|
|
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
|
}
|
|
put_utf8(out, cp);
|
|
break;
|
|
}
|
|
default: return fail(err, "bad escape");
|
|
}
|
|
}
|
|
return fail(err, "unterminated string");
|
|
}
|
|
bool value(Value& v, std::string& err) {
|
|
if (pos_ >= s_.size()) return fail(err, "unexpected end");
|
|
char c = s_[pos_];
|
|
if (c == '{') {
|
|
++pos_;
|
|
v.kind = Value::Kind::Object;
|
|
skip();
|
|
if (literal("}")) return true;
|
|
for (;;) {
|
|
skip();
|
|
std::string key;
|
|
if (!string(key, err)) return false;
|
|
skip();
|
|
if (!literal(":")) return fail(err, "expected ':'");
|
|
skip();
|
|
Value m;
|
|
if (!value(m, err)) return false;
|
|
v.members.emplace_back(std::move(key), std::move(m));
|
|
skip();
|
|
if (literal(",")) continue;
|
|
if (literal("}")) return true;
|
|
return fail(err, "expected ',' or '}'");
|
|
}
|
|
}
|
|
if (c == '[') {
|
|
++pos_;
|
|
v.kind = Value::Kind::Array;
|
|
skip();
|
|
if (literal("]")) return true;
|
|
for (;;) {
|
|
skip();
|
|
Value item;
|
|
if (!value(item, err)) return false;
|
|
v.items.push_back(std::move(item));
|
|
skip();
|
|
if (literal(",")) continue;
|
|
if (literal("]")) return true;
|
|
return fail(err, "expected ',' or ']'");
|
|
}
|
|
}
|
|
if (c == '"') {
|
|
v.kind = Value::Kind::String;
|
|
return string(v.text, err);
|
|
}
|
|
if (literal("null")) return true;
|
|
if (literal("true")) {
|
|
v.kind = Value::Kind::Bool;
|
|
v.b = true;
|
|
return true;
|
|
}
|
|
if (literal("false")) {
|
|
v.kind = Value::Kind::Bool;
|
|
v.b = false;
|
|
return true;
|
|
}
|
|
std::size_t start = pos_;
|
|
while (pos_ < s_.size()) {
|
|
char d = s_[pos_];
|
|
if ((d >= '0' && d <= '9') || d == '-' || d == '+' || d == '.' || d == 'e' || d == 'E') ++pos_;
|
|
else break;
|
|
}
|
|
if (start == pos_) return fail(err, "unexpected character");
|
|
v.kind = Value::Kind::Number;
|
|
v.text = std::string(s_.substr(start, pos_ - start));
|
|
v.num = std::strtod(v.text.c_str(), nullptr);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
inline bool parse(std::string_view text, Value& out, std::string& err) { return Parser(text).parse(out, err); }
|
|
|
|
// Writer helpers for the dump tool.
|
|
inline std::string quote(std::string_view s) {
|
|
std::string out = "\"";
|
|
for (unsigned char c : s) {
|
|
switch (c) {
|
|
case '"': out += "\\\""; break;
|
|
case '\\': out += "\\\\"; break;
|
|
case '\n': out += "\\n"; break;
|
|
case '\r': out += "\\r"; break;
|
|
case '\t': out += "\\t"; break;
|
|
default:
|
|
if (c < 0x20) {
|
|
char buf[8];
|
|
std::snprintf(buf, sizeof buf, "\\u%04x", c);
|
|
out += buf;
|
|
} else {
|
|
out += static_cast<char>(c);
|
|
}
|
|
}
|
|
}
|
|
return out + "\"";
|
|
}
|
|
|
|
} // namespace json_min
|