890 lines
29 KiB
C++
890 lines
29 KiB
C++
#include "vf/Json.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
namespace vf {
|
|
|
|
namespace {
|
|
|
|
void appendUtf8(uint32_t codePoint, std::string& out) {
|
|
if (codePoint < 0x80u) {
|
|
out.push_back(static_cast<char>(codePoint));
|
|
} else if (codePoint < 0x800u) {
|
|
out.push_back(static_cast<char>(0xC0u | (codePoint >> 6)));
|
|
out.push_back(static_cast<char>(0x80u | (codePoint & 0x3Fu)));
|
|
} else if (codePoint < 0x10000u) {
|
|
out.push_back(static_cast<char>(0xE0u | (codePoint >> 12)));
|
|
out.push_back(static_cast<char>(0x80u | ((codePoint >> 6) & 0x3Fu)));
|
|
out.push_back(static_cast<char>(0x80u | (codePoint & 0x3Fu)));
|
|
} else {
|
|
out.push_back(static_cast<char>(0xF0u | (codePoint >> 18)));
|
|
out.push_back(static_cast<char>(0x80u | ((codePoint >> 12) & 0x3Fu)));
|
|
out.push_back(static_cast<char>(0x80u | ((codePoint >> 6) & 0x3Fu)));
|
|
out.push_back(static_cast<char>(0x80u | (codePoint & 0x3Fu)));
|
|
}
|
|
}
|
|
|
|
// Bytes consumed, or 0 when the sequence at `at` is not well formed UTF-8.
|
|
// Overlong forms, encoded surrogates and anything above U+10FFFF are refused.
|
|
std::size_t decodeUtf8(const std::string& text, std::size_t at, uint32_t& codePoint) {
|
|
unsigned char lead = static_cast<unsigned char>(text[at]);
|
|
std::size_t length = 0;
|
|
uint32_t value = 0;
|
|
if (lead < 0x80u) {
|
|
codePoint = lead;
|
|
return 1u;
|
|
}
|
|
if ((lead & 0xE0u) == 0xC0u) {
|
|
length = 2u;
|
|
value = lead & 0x1Fu;
|
|
} else if ((lead & 0xF0u) == 0xE0u) {
|
|
length = 3u;
|
|
value = lead & 0x0Fu;
|
|
} else if ((lead & 0xF8u) == 0xF0u) {
|
|
length = 4u;
|
|
value = lead & 0x07u;
|
|
} else {
|
|
return 0u;
|
|
}
|
|
if (at + length > text.size()) return 0u;
|
|
for (std::size_t i = 1u; i < length; ++i) {
|
|
unsigned char next = static_cast<unsigned char>(text[at + i]);
|
|
if ((next & 0xC0u) != 0x80u) return 0u;
|
|
value = (value << 6) | (next & 0x3Fu);
|
|
}
|
|
if (length == 2u && value < 0x80u) return 0u;
|
|
if (length == 3u && value < 0x800u) return 0u;
|
|
if (length == 4u && value < 0x10000u) return 0u;
|
|
if (value > 0x10FFFFu) return 0u;
|
|
if (value >= 0xD800u && value <= 0xDFFFu) return 0u;
|
|
codePoint = value;
|
|
return length;
|
|
}
|
|
|
|
int hexDigitValue(char c) {
|
|
if (c >= '0' && c <= '9') return c - '0';
|
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
class Parser {
|
|
public:
|
|
Parser(const std::string& text, int maxDepth) : src(text), depthLimit(maxDepth) {}
|
|
|
|
JsonParseResult run() {
|
|
JsonParseResult parsed;
|
|
skipWhitespace();
|
|
if (atEnd()) {
|
|
fail("unexpected end of input, expected a value");
|
|
parsed.error = error;
|
|
return parsed;
|
|
}
|
|
JsonValue root;
|
|
if (!parseValue(1, root)) {
|
|
parsed.error = error;
|
|
return parsed;
|
|
}
|
|
skipWhitespace();
|
|
if (!atEnd()) {
|
|
fail("trailing character after the document root");
|
|
parsed.error = error;
|
|
return parsed;
|
|
}
|
|
parsed.ok = true;
|
|
parsed.root = std::move(root);
|
|
return parsed;
|
|
}
|
|
|
|
private:
|
|
const std::string& src;
|
|
std::size_t cursor = 0u;
|
|
int line = 1;
|
|
int column = 1;
|
|
int depthLimit = jsonDefaultMaxDepth;
|
|
JsonParseError error;
|
|
|
|
bool atEnd() const { return cursor >= src.size(); }
|
|
char peek() const { return src[cursor]; }
|
|
|
|
char advance() {
|
|
char c = src[cursor++];
|
|
if (c == '\n') {
|
|
++line;
|
|
column = 1;
|
|
} else {
|
|
++column;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
void skipWhitespace() {
|
|
while (!atEnd()) {
|
|
char c = peek();
|
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
|
|
advance();
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void fail(const std::string& message) {
|
|
if (error.present()) return;
|
|
error.line = line;
|
|
error.column = column;
|
|
error.message = message;
|
|
}
|
|
|
|
void failAt(int atLine, int atColumn, const std::string& message) {
|
|
if (error.present()) return;
|
|
error.line = atLine;
|
|
error.column = atColumn;
|
|
error.message = message;
|
|
}
|
|
|
|
bool expect(char c, const std::string& message) {
|
|
if (atEnd() || peek() != c) {
|
|
fail(message);
|
|
return false;
|
|
}
|
|
advance();
|
|
return true;
|
|
}
|
|
|
|
bool parseValue(int depth, JsonValue& out) {
|
|
if (atEnd()) {
|
|
fail("unexpected end of input, expected a value");
|
|
return false;
|
|
}
|
|
char c = peek();
|
|
if (c == '{') return parseObject(depth, out);
|
|
if (c == '[') return parseArray(depth, out);
|
|
if (c == '"') {
|
|
std::string text;
|
|
if (!parseString(text)) return false;
|
|
out = JsonValue::makeString(std::move(text));
|
|
return true;
|
|
}
|
|
if (c == '-' || (c >= '0' && c <= '9')) return parseNumber(out);
|
|
if (c == 't') return parseLiteral("true", JsonValue::makeBool(true), out);
|
|
if (c == 'f') return parseLiteral("false", JsonValue::makeBool(false), out);
|
|
if (c == 'n') return parseLiteral("null", JsonValue::makeNull(), out);
|
|
fail(std::string("unexpected character '") + c + "', expected a value");
|
|
return false;
|
|
}
|
|
|
|
bool parseLiteral(const char* word, JsonValue value, JsonValue& out) {
|
|
std::size_t length = std::strlen(word);
|
|
if (src.compare(cursor, length, word) != 0) {
|
|
fail(std::string("unknown literal, expected ") + word);
|
|
return false;
|
|
}
|
|
for (std::size_t i = 0u; i < length; ++i) advance();
|
|
out = std::move(value);
|
|
return true;
|
|
}
|
|
|
|
bool parseObject(int depth, JsonValue& out) {
|
|
if (depth > depthLimit) {
|
|
fail("nesting deeper than the " + std::to_string(depthLimit) + " level limit");
|
|
return false;
|
|
}
|
|
advance();
|
|
out = JsonValue::makeObject();
|
|
skipWhitespace();
|
|
if (!atEnd() && peek() == '}') {
|
|
advance();
|
|
return true;
|
|
}
|
|
for (;;) {
|
|
skipWhitespace();
|
|
if (atEnd() || peek() != '"') {
|
|
fail("expected a quoted member name");
|
|
return false;
|
|
}
|
|
std::string key;
|
|
if (!parseString(key)) return false;
|
|
skipWhitespace();
|
|
if (!expect(':', "expected ':' after the member name")) return false;
|
|
skipWhitespace();
|
|
JsonValue member;
|
|
if (!parseValue(depth + 1, member)) return false;
|
|
out.set(key, std::move(member));
|
|
skipWhitespace();
|
|
if (atEnd()) {
|
|
fail("unexpected end of input, expected ',' or '}'");
|
|
return false;
|
|
}
|
|
char c = peek();
|
|
if (c == ',') {
|
|
advance();
|
|
skipWhitespace();
|
|
if (!atEnd() && peek() == '}') {
|
|
fail("trailing comma before '}'");
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
if (c == '}') {
|
|
advance();
|
|
return true;
|
|
}
|
|
fail(std::string("unexpected character '") + c + "', expected ',' or '}'");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool parseArray(int depth, JsonValue& out) {
|
|
if (depth > depthLimit) {
|
|
fail("nesting deeper than the " + std::to_string(depthLimit) + " level limit");
|
|
return false;
|
|
}
|
|
advance();
|
|
out = JsonValue::makeArray();
|
|
skipWhitespace();
|
|
if (!atEnd() && peek() == ']') {
|
|
advance();
|
|
return true;
|
|
}
|
|
for (;;) {
|
|
skipWhitespace();
|
|
JsonValue element;
|
|
if (!parseValue(depth + 1, element)) return false;
|
|
out.push(std::move(element));
|
|
skipWhitespace();
|
|
if (atEnd()) {
|
|
fail("unexpected end of input, expected ',' or ']'");
|
|
return false;
|
|
}
|
|
char c = peek();
|
|
if (c == ',') {
|
|
advance();
|
|
skipWhitespace();
|
|
if (!atEnd() && peek() == ']') {
|
|
fail("trailing comma before ']'");
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
if (c == ']') {
|
|
advance();
|
|
return true;
|
|
}
|
|
fail(std::string("unexpected character '") + c + "', expected ',' or ']'");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool parseHexQuad(uint32_t& out) {
|
|
uint32_t value = 0u;
|
|
for (int i = 0; i < 4; ++i) {
|
|
if (atEnd()) {
|
|
fail("unexpected end of input inside a \\u escape");
|
|
return false;
|
|
}
|
|
int digit = hexDigitValue(peek());
|
|
if (digit < 0) {
|
|
fail(std::string("'") + peek() + "' is not a hexadecimal digit");
|
|
return false;
|
|
}
|
|
value = (value << 4) | static_cast<uint32_t>(digit);
|
|
advance();
|
|
}
|
|
out = value;
|
|
return true;
|
|
}
|
|
|
|
bool parseEscape(std::string& out) {
|
|
if (atEnd()) {
|
|
fail("unexpected end of input after '\\'");
|
|
return false;
|
|
}
|
|
int escapeLine = line;
|
|
int escapeColumn = column;
|
|
char c = advance();
|
|
switch (c) {
|
|
case '"': out.push_back('"'); return true;
|
|
case '\\': out.push_back('\\'); return true;
|
|
case '/': out.push_back('/'); return true;
|
|
case 'b': out.push_back('\b'); return true;
|
|
case 'f': out.push_back('\f'); return true;
|
|
case 'n': out.push_back('\n'); return true;
|
|
case 'r': out.push_back('\r'); return true;
|
|
case 't': out.push_back('\t'); return true;
|
|
case 'u': break;
|
|
default:
|
|
failAt(escapeLine, escapeColumn,
|
|
std::string("unknown escape sequence '\\") + c + "'");
|
|
return false;
|
|
}
|
|
uint32_t unit = 0u;
|
|
if (!parseHexQuad(unit)) return false;
|
|
if (unit >= 0xDC00u && unit <= 0xDFFFu) {
|
|
failAt(escapeLine, escapeColumn, "low surrogate without a preceding high surrogate");
|
|
return false;
|
|
}
|
|
if (unit >= 0xD800u && unit <= 0xDBFFu) {
|
|
if (cursor + 1u >= src.size() || src[cursor] != '\\' || src[cursor + 1u] != 'u') {
|
|
failAt(escapeLine, escapeColumn, "high surrogate without a following low surrogate");
|
|
return false;
|
|
}
|
|
advance();
|
|
advance();
|
|
uint32_t low = 0u;
|
|
if (!parseHexQuad(low)) return false;
|
|
if (low < 0xDC00u || low > 0xDFFFu) {
|
|
failAt(escapeLine, escapeColumn, "high surrogate followed by a non low surrogate");
|
|
return false;
|
|
}
|
|
uint32_t combined = 0x10000u + ((unit - 0xD800u) << 10) + (low - 0xDC00u);
|
|
appendUtf8(combined, out);
|
|
return true;
|
|
}
|
|
appendUtf8(unit, out);
|
|
return true;
|
|
}
|
|
|
|
bool parseString(std::string& out) {
|
|
advance();
|
|
out.clear();
|
|
for (;;) {
|
|
if (atEnd()) {
|
|
fail("unexpected end of input inside a string");
|
|
return false;
|
|
}
|
|
unsigned char c = static_cast<unsigned char>(peek());
|
|
if (c == '"') {
|
|
advance();
|
|
return true;
|
|
}
|
|
if (c == '\\') {
|
|
advance();
|
|
if (!parseEscape(out)) return false;
|
|
continue;
|
|
}
|
|
if (c < 0x20u) {
|
|
fail("raw control character in a string, escape it as \\u00" +
|
|
std::string(1, "0123456789abcdef"[(c >> 4) & 0xFu]) +
|
|
std::string(1, "0123456789abcdef"[c & 0xFu]));
|
|
return false;
|
|
}
|
|
if (c < 0x80u) {
|
|
out.push_back(static_cast<char>(c));
|
|
advance();
|
|
continue;
|
|
}
|
|
uint32_t codePoint = 0u;
|
|
std::size_t length = decodeUtf8(src, cursor, codePoint);
|
|
if (length == 0u) {
|
|
fail("malformed UTF-8 byte sequence in a string");
|
|
return false;
|
|
}
|
|
for (std::size_t i = 0u; i < length; ++i) out.push_back(advance());
|
|
}
|
|
}
|
|
|
|
bool parseNumber(JsonValue& out) {
|
|
std::size_t start = cursor;
|
|
int startLine = line;
|
|
int startColumn = column;
|
|
bool negative = false;
|
|
if (peek() == '-') {
|
|
negative = true;
|
|
advance();
|
|
}
|
|
if (atEnd()) {
|
|
fail("unexpected end of input inside a number");
|
|
return false;
|
|
}
|
|
if (peek() == '0') {
|
|
advance();
|
|
if (!atEnd() && peek() >= '0' && peek() <= '9') {
|
|
fail("leading zero in a number");
|
|
return false;
|
|
}
|
|
} else if (peek() >= '1' && peek() <= '9') {
|
|
while (!atEnd() && peek() >= '0' && peek() <= '9') advance();
|
|
} else {
|
|
fail("expected a digit after the sign");
|
|
return false;
|
|
}
|
|
|
|
bool fractional = false;
|
|
if (!atEnd() && peek() == '.') {
|
|
fractional = true;
|
|
advance();
|
|
if (atEnd() || peek() < '0' || peek() > '9') {
|
|
fail("expected a digit after the decimal point");
|
|
return false;
|
|
}
|
|
while (!atEnd() && peek() >= '0' && peek() <= '9') advance();
|
|
}
|
|
if (!atEnd() && (peek() == 'e' || peek() == 'E')) {
|
|
fractional = true;
|
|
advance();
|
|
if (!atEnd() && (peek() == '+' || peek() == '-')) advance();
|
|
if (atEnd() || peek() < '0' || peek() > '9') {
|
|
fail("expected a digit in the exponent");
|
|
return false;
|
|
}
|
|
while (!atEnd() && peek() >= '0' && peek() <= '9') advance();
|
|
}
|
|
|
|
std::string token = src.substr(start, cursor - start);
|
|
if (!fractional) {
|
|
const unsigned long long limit =
|
|
negative ? 9223372036854775808ull : 9223372036854775807ull;
|
|
unsigned long long magnitude = 0ull;
|
|
bool overflow = false;
|
|
for (char c : token) {
|
|
if (c == '-') continue;
|
|
magnitude = magnitude * 10ull + static_cast<unsigned long long>(c - '0');
|
|
if (magnitude > limit) {
|
|
overflow = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!overflow) {
|
|
if (magnitude == 9223372036854775808ull) {
|
|
out = JsonValue::makeInt(-9223372036854775807ll - 1ll);
|
|
} else {
|
|
long long whole = static_cast<long long>(magnitude);
|
|
out = JsonValue::makeInt(negative ? -whole : whole);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
double value = std::strtod(token.c_str(), nullptr);
|
|
if (!std::isfinite(value)) {
|
|
failAt(startLine, startColumn, "number out of the representable range");
|
|
return false;
|
|
}
|
|
out = JsonValue::makeNumber(value);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
std::string formatNumber(const JsonValue& value) {
|
|
if (value.isInteger()) return std::to_string(value.intValue());
|
|
double raw = value.numberValue();
|
|
if (!std::isfinite(raw)) return "0";
|
|
char buffer[48];
|
|
for (int precision = 15; precision <= 17; ++precision) {
|
|
std::snprintf(buffer, sizeof buffer, "%.*g", precision, raw);
|
|
if (std::strtod(buffer, nullptr) == raw) break;
|
|
}
|
|
std::string text(buffer);
|
|
// A double that prints without a fraction would come back as an integer and
|
|
// print differently the next time round, so the writer keeps it a double.
|
|
if (text.find_first_of(".eE") == std::string::npos) text += ".0";
|
|
return text;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
JsonValue JsonValue::makeNull() { return JsonValue(); }
|
|
|
|
JsonValue JsonValue::makeBool(bool value) {
|
|
JsonValue made;
|
|
made.type = JsonType::Bool;
|
|
made.boolPayload = value;
|
|
return made;
|
|
}
|
|
|
|
JsonValue JsonValue::makeNumber(double value) {
|
|
JsonValue made;
|
|
made.type = JsonType::Number;
|
|
made.numberPayload = value;
|
|
return made;
|
|
}
|
|
|
|
JsonValue JsonValue::makeInt(long long value) {
|
|
JsonValue made;
|
|
made.type = JsonType::Number;
|
|
made.integral = true;
|
|
made.integerPayload = value;
|
|
made.numberPayload = static_cast<double>(value);
|
|
return made;
|
|
}
|
|
|
|
JsonValue JsonValue::makeString(std::string value) {
|
|
JsonValue made;
|
|
made.type = JsonType::String;
|
|
made.stringPayload = std::move(value);
|
|
return made;
|
|
}
|
|
|
|
JsonValue JsonValue::makeArray() {
|
|
JsonValue made;
|
|
made.type = JsonType::Array;
|
|
return made;
|
|
}
|
|
|
|
JsonValue JsonValue::makeObject() {
|
|
JsonValue made;
|
|
made.type = JsonType::Object;
|
|
return made;
|
|
}
|
|
|
|
double JsonValue::numberValue() const {
|
|
if (type != JsonType::Number) return 0.0;
|
|
return integral ? static_cast<double>(integerPayload) : numberPayload;
|
|
}
|
|
|
|
long long JsonValue::intValue() const {
|
|
if (type != JsonType::Number) return 0;
|
|
if (integral) return integerPayload;
|
|
double rounded = numberPayload < 0.0 ? std::ceil(numberPayload - 0.5)
|
|
: std::floor(numberPayload + 0.5);
|
|
if (rounded >= 9223372036854775296.0) return 9223372036854775807ll;
|
|
if (rounded <= -9223372036854775296.0) return -9223372036854775807ll - 1ll;
|
|
return static_cast<long long>(rounded);
|
|
}
|
|
|
|
std::size_t JsonValue::size() const {
|
|
if (type == JsonType::Array) return arrayPayload.size();
|
|
if (type == JsonType::Object) return objectPayload.size();
|
|
return 0u;
|
|
}
|
|
|
|
void JsonValue::push(JsonValue value) {
|
|
if (type != JsonType::Array) {
|
|
*this = makeArray();
|
|
}
|
|
arrayPayload.push_back(std::move(value));
|
|
}
|
|
|
|
void JsonValue::set(const std::string& key, JsonValue value) {
|
|
if (type != JsonType::Object) {
|
|
*this = makeObject();
|
|
}
|
|
for (Member& member : objectPayload) {
|
|
if (member.first == key) {
|
|
member.second = std::move(value);
|
|
return;
|
|
}
|
|
}
|
|
objectPayload.emplace_back(key, std::move(value));
|
|
}
|
|
|
|
bool JsonValue::erase(const std::string& key) {
|
|
for (std::size_t i = 0u; i < objectPayload.size(); ++i) {
|
|
if (objectPayload[i].first == key) {
|
|
objectPayload.erase(objectPayload.begin() + static_cast<std::ptrdiff_t>(i));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const JsonValue* JsonValue::find(const std::string& key) const {
|
|
for (const Member& member : objectPayload)
|
|
if (member.first == key) return &member.second;
|
|
return nullptr;
|
|
}
|
|
|
|
JsonValue* JsonValue::find(const std::string& key) {
|
|
for (Member& member : objectPayload)
|
|
if (member.first == key) return &member.second;
|
|
return nullptr;
|
|
}
|
|
|
|
const JsonValue* JsonValue::at(std::size_t index) const {
|
|
if (type != JsonType::Array || index >= arrayPayload.size()) return nullptr;
|
|
return &arrayPayload[index];
|
|
}
|
|
|
|
bool JsonValue::boolOr(bool fallback) const { return isBool() ? boolPayload : fallback; }
|
|
|
|
double JsonValue::numberOr(double fallback) const { return isNumber() ? numberValue() : fallback; }
|
|
|
|
long long JsonValue::intOr(long long fallback) const { return isNumber() ? intValue() : fallback; }
|
|
|
|
std::string JsonValue::stringOr(const std::string& fallback) const {
|
|
return isString() ? stringPayload : fallback;
|
|
}
|
|
|
|
bool JsonValue::boolOr(const std::string& key, bool fallback) const {
|
|
const JsonValue* member = find(key);
|
|
return member ? member->boolOr(fallback) : fallback;
|
|
}
|
|
|
|
double JsonValue::numberOr(const std::string& key, double fallback) const {
|
|
const JsonValue* member = find(key);
|
|
return member ? member->numberOr(fallback) : fallback;
|
|
}
|
|
|
|
long long JsonValue::intOr(const std::string& key, long long fallback) const {
|
|
const JsonValue* member = find(key);
|
|
return member ? member->intOr(fallback) : fallback;
|
|
}
|
|
|
|
std::string JsonValue::stringOr(const std::string& key, const std::string& fallback) const {
|
|
const JsonValue* member = find(key);
|
|
return member ? member->stringOr(fallback) : fallback;
|
|
}
|
|
|
|
bool JsonValue::operator==(const JsonValue& other) const {
|
|
if (type != other.type) return false;
|
|
switch (type) {
|
|
case JsonType::Null: return true;
|
|
case JsonType::Bool: return boolPayload == other.boolPayload;
|
|
case JsonType::Number: return numberValue() == other.numberValue();
|
|
case JsonType::String: return stringPayload == other.stringPayload;
|
|
case JsonType::Array: return arrayPayload == other.arrayPayload;
|
|
case JsonType::Object: return objectPayload == other.objectPayload;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::string JsonParseError::text() const {
|
|
if (!present()) return "no error";
|
|
return std::to_string(line) + ":" + std::to_string(column) + ": " + message;
|
|
}
|
|
|
|
JsonParseResult parseJson(const std::string& text, int maxDepth) {
|
|
Parser parser(text, maxDepth < 1 ? 1 : maxDepth);
|
|
return parser.run();
|
|
}
|
|
|
|
void JsonWriter::writeString(const std::string& value, std::string& out) const {
|
|
static const char* hexDigits = "0123456789abcdef";
|
|
out.push_back('"');
|
|
std::size_t at = 0u;
|
|
while (at < value.size()) {
|
|
unsigned char c = static_cast<unsigned char>(value[at]);
|
|
if (c == '"' || c == '\\') {
|
|
out.push_back('\\');
|
|
out.push_back(static_cast<char>(c));
|
|
++at;
|
|
continue;
|
|
}
|
|
if (c == '\b') { out += "\\b"; ++at; continue; }
|
|
if (c == '\f') { out += "\\f"; ++at; continue; }
|
|
if (c == '\n') { out += "\\n"; ++at; continue; }
|
|
if (c == '\r') { out += "\\r"; ++at; continue; }
|
|
if (c == '\t') { out += "\\t"; ++at; continue; }
|
|
if (c < 0x20u) {
|
|
out += "\\u00";
|
|
out.push_back(hexDigits[(c >> 4) & 0xFu]);
|
|
out.push_back(hexDigits[c & 0xFu]);
|
|
++at;
|
|
continue;
|
|
}
|
|
if (c < 0x80u || !escapeNonAscii) {
|
|
out.push_back(value[at]);
|
|
++at;
|
|
continue;
|
|
}
|
|
uint32_t codePoint = 0u;
|
|
std::size_t length = decodeUtf8(value, at, codePoint);
|
|
if (length == 0u) {
|
|
// Only reachable for a hand built string that never went through the
|
|
// parser; the byte is kept so nothing is silently rewritten.
|
|
out.push_back(value[at]);
|
|
++at;
|
|
continue;
|
|
}
|
|
if (codePoint >= 0x10000u) {
|
|
uint32_t rest = codePoint - 0x10000u;
|
|
uint32_t high = 0xD800u + (rest >> 10);
|
|
uint32_t low = 0xDC00u + (rest & 0x3FFu);
|
|
for (uint32_t unit : {high, low}) {
|
|
out += "\\u";
|
|
for (int shift = 12; shift >= 0; shift -= 4)
|
|
out.push_back(hexDigits[(unit >> shift) & 0xFu]);
|
|
}
|
|
} else {
|
|
out += "\\u";
|
|
for (int shift = 12; shift >= 0; shift -= 4)
|
|
out.push_back(hexDigits[(codePoint >> shift) & 0xFu]);
|
|
}
|
|
at += length;
|
|
}
|
|
out.push_back('"');
|
|
}
|
|
|
|
void JsonWriter::writeValue(const JsonValue& value, int depth, std::string& out) const {
|
|
const bool pretty = indentWidth > 0;
|
|
const std::string inner = pretty ? std::string(static_cast<std::size_t>(indentWidth * (depth + 1)), ' ')
|
|
: std::string();
|
|
const std::string outer = pretty ? std::string(static_cast<std::size_t>(indentWidth * depth), ' ')
|
|
: std::string();
|
|
switch (value.kind()) {
|
|
case JsonType::Null: out += "null"; return;
|
|
case JsonType::Bool: out += value.boolValue() ? "true" : "false"; return;
|
|
case JsonType::Number: out += formatNumber(value); return;
|
|
case JsonType::String: writeString(value.stringValue(), out); return;
|
|
case JsonType::Array: {
|
|
if (value.elements().empty()) {
|
|
out += "[]";
|
|
return;
|
|
}
|
|
out.push_back('[');
|
|
bool first = true;
|
|
for (const JsonValue& element : value.elements()) {
|
|
if (!first) out.push_back(',');
|
|
first = false;
|
|
if (pretty) {
|
|
out.push_back('\n');
|
|
out += inner;
|
|
}
|
|
writeValue(element, depth + 1, out);
|
|
}
|
|
if (pretty) {
|
|
out.push_back('\n');
|
|
out += outer;
|
|
}
|
|
out.push_back(']');
|
|
return;
|
|
}
|
|
case JsonType::Object: {
|
|
if (value.members().empty()) {
|
|
out += "{}";
|
|
return;
|
|
}
|
|
out.push_back('{');
|
|
bool first = true;
|
|
for (const JsonValue::Member& member : value.members()) {
|
|
if (!first) out.push_back(',');
|
|
first = false;
|
|
if (pretty) {
|
|
out.push_back('\n');
|
|
out += inner;
|
|
}
|
|
writeString(member.first, out);
|
|
out.push_back(':');
|
|
if (pretty) out.push_back(' ');
|
|
writeValue(member.second, depth + 1, out);
|
|
}
|
|
if (pretty) {
|
|
out.push_back('\n');
|
|
out += outer;
|
|
}
|
|
out.push_back('}');
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string JsonWriter::write(const JsonValue& value) const {
|
|
std::string out;
|
|
writeValue(value, 0, out);
|
|
return out;
|
|
}
|
|
|
|
std::string JsonWriter::compact(const JsonValue& value) {
|
|
JsonWriter writer;
|
|
return writer.write(value);
|
|
}
|
|
|
|
std::string JsonWriter::indented(const JsonValue& value, int indentWidth) {
|
|
JsonWriter writer;
|
|
writer.indentWidth = indentWidth;
|
|
return writer.write(value);
|
|
}
|
|
|
|
JsonPointer::JsonPointer(const std::string& pointer) {
|
|
if (pointer.empty()) return;
|
|
if (pointer[0] != '/') {
|
|
wellFormed = false;
|
|
return;
|
|
}
|
|
std::string token;
|
|
for (std::size_t i = 1u; i <= pointer.size(); ++i) {
|
|
if (i == pointer.size() || pointer[i] == '/') {
|
|
parts.push_back(token);
|
|
token.clear();
|
|
continue;
|
|
}
|
|
if (pointer[i] == '~') {
|
|
if (i + 1u >= pointer.size()) {
|
|
wellFormed = false;
|
|
return;
|
|
}
|
|
char next = pointer[i + 1u];
|
|
if (next == '0') {
|
|
token.push_back('~');
|
|
} else if (next == '1') {
|
|
token.push_back('/');
|
|
} else {
|
|
wellFormed = false;
|
|
return;
|
|
}
|
|
++i;
|
|
continue;
|
|
}
|
|
token.push_back(pointer[i]);
|
|
}
|
|
}
|
|
|
|
std::string JsonPointer::text() const {
|
|
std::string out;
|
|
for (const std::string& token : parts) {
|
|
out.push_back('/');
|
|
for (char c : token) {
|
|
if (c == '~') {
|
|
out += "~0";
|
|
} else if (c == '/') {
|
|
out += "~1";
|
|
} else {
|
|
out.push_back(c);
|
|
}
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool arrayIndexOf(const std::string& token, std::size_t& out) {
|
|
if (token.empty()) return false;
|
|
if (token.size() > 1u && token[0] == '0') return false;
|
|
std::size_t index = 0u;
|
|
for (char c : token) {
|
|
if (c < '0' || c > '9') return false;
|
|
index = index * 10u + static_cast<std::size_t>(c - '0');
|
|
if (index > 100000000u) return false;
|
|
}
|
|
out = index;
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const JsonValue* JsonPointer::resolve(const JsonValue& root) const {
|
|
if (!wellFormed) return nullptr;
|
|
const JsonValue* here = &root;
|
|
for (const std::string& token : parts) {
|
|
if (here->isObject()) {
|
|
here = here->find(token);
|
|
if (here == nullptr) return nullptr;
|
|
continue;
|
|
}
|
|
if (here->isArray()) {
|
|
std::size_t index = 0u;
|
|
if (!arrayIndexOf(token, index)) return nullptr;
|
|
here = here->at(index);
|
|
if (here == nullptr) return nullptr;
|
|
continue;
|
|
}
|
|
return nullptr;
|
|
}
|
|
return here;
|
|
}
|
|
|
|
JsonValue* JsonPointer::resolve(JsonValue& root) const {
|
|
return const_cast<JsonValue*>(resolve(static_cast<const JsonValue&>(root)));
|
|
}
|
|
|
|
const JsonValue* JsonPointer::find(const JsonValue& root, const std::string& pointer) {
|
|
JsonPointer path(pointer);
|
|
return path.resolve(root);
|
|
}
|
|
|
|
} // namespace vf
|