feat: add appendUtf8 and hexDigitValue
This commit is contained in:
1 parent
4bd3606483
commit
fde84ea3d7
1 file changed
+157
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user