diff --git a/core/src/Json.cpp b/core/src/Json.cpp index 11c938f..a4bd20c 100644 --- a/core/src/Json.cpp +++ b/core/src/Json.cpp @@ -298,3 +298,173 @@ private: 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(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(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(c - '0'); + if (magnitude > limit) { + overflow = true; + break; + } + } + if (!overflow) { + if (magnitude == 9223372036854775808ull) { + out = JsonValue::makeInt(-9223372036854775807ll - 1ll); + } else { + long long whole = static_cast(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; + } +}; +