From fde84ea3d7743616ff113b9e36c993cb00dbfcb9 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 7 Sep 2026 22:54:39 +0200 Subject: [PATCH] feat: add appendUtf8 and hexDigitValue --- core/src/Json.cpp | 157 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 core/src/Json.cpp diff --git a/core/src/Json.cpp b/core/src/Json.cpp new file mode 100644 index 0000000..fe35290 --- /dev/null +++ b/core/src/Json.cpp @@ -0,0 +1,157 @@ +#include "vf/Json.h" + +#include +#include +#include +#include + +namespace vf { + +namespace { + +void appendUtf8(uint32_t codePoint, std::string& out) { + if (codePoint < 0x80u) { + out.push_back(static_cast(codePoint)); + } else if (codePoint < 0x800u) { + out.push_back(static_cast(0xC0u | (codePoint >> 6))); + out.push_back(static_cast(0x80u | (codePoint & 0x3Fu))); + } else if (codePoint < 0x10000u) { + out.push_back(static_cast(0xE0u | (codePoint >> 12))); + out.push_back(static_cast(0x80u | ((codePoint >> 6) & 0x3Fu))); + out.push_back(static_cast(0x80u | (codePoint & 0x3Fu))); + } else { + out.push_back(static_cast(0xF0u | (codePoint >> 18))); + out.push_back(static_cast(0x80u | ((codePoint >> 12) & 0x3Fu))); + out.push_back(static_cast(0x80u | ((codePoint >> 6) & 0x3Fu))); + out.push_back(static_cast(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(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(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; + } +