This commit is contained in:
2026-05-03 11:16:56 +10:00
parent f6db9ee3e6
commit ee929374a8
14 changed files with 873 additions and 350 deletions

View File

@@ -1,6 +1,7 @@
#include "stdafx.h"
#include "RuntimeJson.h"
#include <cerrno>
#include <cctype>
#include <cmath>
#include <cstring>
@@ -10,6 +11,59 @@
namespace
{
int HexDigitValue(char ch)
{
if (ch >= '0' && ch <= '9')
return ch - '0';
if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
return -1;
}
bool IsHighSurrogate(unsigned int codePoint)
{
return codePoint >= 0xD800 && codePoint <= 0xDBFF;
}
bool IsLowSurrogate(unsigned int codePoint)
{
return codePoint >= 0xDC00 && codePoint <= 0xDFFF;
}
void AppendUtf8(unsigned int codePoint, std::ostringstream& output)
{
if (codePoint <= 0x7F)
{
output << static_cast<char>(codePoint);
}
else if (codePoint <= 0x7FF)
{
output << static_cast<char>(0xC0 | ((codePoint >> 6) & 0x1F));
output << static_cast<char>(0x80 | (codePoint & 0x3F));
}
else if (codePoint <= 0xFFFF)
{
output << static_cast<char>(0xE0 | ((codePoint >> 12) & 0x0F));
output << static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F));
output << static_cast<char>(0x80 | (codePoint & 0x3F));
}
else
{
output << static_cast<char>(0xF0 | ((codePoint >> 18) & 0x07));
output << static_cast<char>(0x80 | ((codePoint >> 12) & 0x3F));
output << static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F));
output << static_cast<char>(0x80 | (codePoint & 0x3F));
}
}
void AppendControlEscape(unsigned char ch, std::ostringstream& output)
{
const char* digits = "0123456789ABCDEF";
output << "\\u00" << digits[(ch >> 4) & 0x0F] << digits[ch & 0x0F];
}
class JsonParser
{
public:
@@ -181,8 +235,9 @@ private:
case 'r': result << '\r'; break;
case 't': result << '\t'; break;
case 'u':
setError("Unicode escape sequences are not supported in this JSON parser.");
return false;
if (!parseUnicodeEscape(result))
return false;
break;
default:
setError("Invalid escape sequence in JSON string.");
return false;
@@ -190,6 +245,11 @@ private:
}
else
{
if (static_cast<unsigned char>(ch) < 0x20)
{
setError("Unescaped control character in JSON string.");
return false;
}
result << ch;
}
}
@@ -198,6 +258,66 @@ private:
return false;
}
bool parseHexCodePoint(unsigned int& codePoint)
{
if (mPosition + 4 > mText.size())
{
setError("Unexpected end of Unicode escape sequence.");
return false;
}
codePoint = 0;
for (int i = 0; i < 4; ++i)
{
const int digit = HexDigitValue(mText[mPosition + i]);
if (digit < 0)
{
setError("Invalid Unicode escape sequence in JSON string.");
return false;
}
codePoint = (codePoint << 4) | static_cast<unsigned int>(digit);
}
mPosition += 4;
return true;
}
bool parseUnicodeEscape(std::ostringstream& result)
{
unsigned int codePoint = 0;
if (!parseHexCodePoint(codePoint))
return false;
if (IsHighSurrogate(codePoint))
{
if (mPosition + 2 > mText.size() || mText[mPosition] != '\\' || mText[mPosition + 1] != 'u')
{
setError("High surrogate Unicode escape must be followed by a low surrogate.");
return false;
}
mPosition += 2;
unsigned int lowSurrogate = 0;
if (!parseHexCodePoint(lowSurrogate))
return false;
if (!IsLowSurrogate(lowSurrogate))
{
setError("High surrogate Unicode escape must be followed by a low surrogate.");
return false;
}
codePoint = 0x10000 + (((codePoint - 0xD800) << 10) | (lowSurrogate - 0xDC00));
}
else if (IsLowSurrogate(codePoint))
{
setError("Low surrogate Unicode escape without preceding high surrogate.");
return false;
}
AppendUtf8(codePoint, result);
return true;
}
bool parseNumber(JsonValue& value)
{
std::size_t start = mPosition;
@@ -205,12 +325,40 @@ private:
if (mText[mPosition] == '-')
++mPosition;
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
if (mPosition >= mText.size())
{
setError("Invalid JSON number.");
return false;
}
if (mText[mPosition] == '0')
{
++mPosition;
if (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
{
setError("JSON numbers must not contain leading zeroes.");
return false;
}
}
else if (mText[mPosition] >= '1' && mText[mPosition] <= '9')
{
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
++mPosition;
}
else
{
setError("Invalid JSON number.");
return false;
}
if (mPosition < mText.size() && mText[mPosition] == '.')
{
++mPosition;
if (mPosition >= mText.size() || !std::isdigit(static_cast<unsigned char>(mText[mPosition])))
{
setError("JSON number fraction must contain at least one digit.");
return false;
}
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
++mPosition;
}
@@ -220,14 +368,20 @@ private:
++mPosition;
if (mPosition < mText.size() && (mText[mPosition] == '+' || mText[mPosition] == '-'))
++mPosition;
if (mPosition >= mText.size() || !std::isdigit(static_cast<unsigned char>(mText[mPosition])))
{
setError("JSON number exponent must contain at least one digit.");
return false;
}
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
++mPosition;
}
std::string token = mText.substr(start, mPosition - start);
char* endPtr = nullptr;
errno = 0;
double parsed = strtod(token.c_str(), &endPtr);
if (endPtr == token.c_str() || *endPtr != '\0')
if (endPtr == token.c_str() || *endPtr != '\0' || errno == ERANGE || !std::isfinite(parsed))
{
setError("Invalid JSON number.");
return false;
@@ -322,7 +476,12 @@ void SerializeJsonImpl(const JsonValue& value, std::ostringstream& output, bool
case '\n': output << "\\n"; break;
case '\r': output << "\\r"; break;
case '\t': output << "\\t"; break;
default: output << ch; break;
default:
if (static_cast<unsigned char>(ch) < 0x20)
AppendControlEscape(static_cast<unsigned char>(ch), output);
else
output << ch;
break;
}
}
output << '"';
@@ -407,14 +566,14 @@ JsonValue::JsonValue(const std::string& value)
JsonValue JsonValue::MakeArray()
{
JsonValue value;
value.mType = Type::Array;
value.reset(Type::Array);
return value;
}
JsonValue JsonValue::MakeObject()
{
JsonValue value;
value.mType = Type::Object;
value.reset(Type::Object);
return value;
}
@@ -449,20 +608,14 @@ const std::map<std::string, JsonValue>& JsonValue::asObject() const
std::vector<JsonValue>& JsonValue::array()
{
if (mType != Type::Array)
{
mType = Type::Array;
mArrayValue.clear();
}
reset(Type::Array);
return mArrayValue;
}
std::map<std::string, JsonValue>& JsonValue::object()
{
if (mType != Type::Object)
{
mType = Type::Object;
mObjectValue.clear();
}
reset(Type::Object);
return mObjectValue;
}
@@ -485,6 +638,16 @@ const JsonValue* JsonValue::find(const std::string& key) const
return iterator != mObjectValue.end() ? &iterator->second : nullptr;
}
void JsonValue::reset(Type type)
{
mType = type;
mBooleanValue = false;
mNumberValue = 0.0;
mStringValue.clear();
mArrayValue.clear();
mObjectValue.clear();
}
bool ParseJson(const std::string& text, JsonValue& value, std::string& error)
{
error.clear();