feat: add appendUtf8 and hexDigitValue

This commit is contained in:
dualfroz committed 2026-09-07 22:54:39 +02:00
1 parent 4bd3606483
commit fde84ea3d7
1 file changed
+157
+157
View File
@@ -0,0 +1,157 @@
#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;
}