re organisation
This commit is contained in:
@@ -0,0 +1,674 @@
|
||||
#include "stdafx.h"
|
||||
#include "RuntimeJson.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
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:
|
||||
JsonParser(const std::string& text, std::string& error)
|
||||
: mText(text), mError(error), mPosition(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool parse(JsonValue& value)
|
||||
{
|
||||
skipWhitespace();
|
||||
if (!parseValue(value))
|
||||
return false;
|
||||
|
||||
skipWhitespace();
|
||||
if (mPosition != mText.size())
|
||||
{
|
||||
setError("Unexpected trailing characters in JSON input.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool parseValue(JsonValue& value)
|
||||
{
|
||||
if (mPosition >= mText.size())
|
||||
{
|
||||
setError("Unexpected end of JSON input.");
|
||||
return false;
|
||||
}
|
||||
|
||||
char ch = mText[mPosition];
|
||||
if (ch == '{')
|
||||
return parseObject(value);
|
||||
if (ch == '[')
|
||||
return parseArray(value);
|
||||
if (ch == '"')
|
||||
{
|
||||
std::string stringValue;
|
||||
if (!parseString(stringValue))
|
||||
return false;
|
||||
value = JsonValue(stringValue);
|
||||
return true;
|
||||
}
|
||||
if (ch == 't')
|
||||
return parseLiteral("true", JsonValue(true), value);
|
||||
if (ch == 'f')
|
||||
return parseLiteral("false", JsonValue(false), value);
|
||||
if (ch == 'n')
|
||||
return parseLiteral("null", JsonValue(), value);
|
||||
if (ch == '-' || std::isdigit(static_cast<unsigned char>(ch)))
|
||||
return parseNumber(value);
|
||||
|
||||
setError("Unexpected token while parsing JSON.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parseObject(JsonValue& value)
|
||||
{
|
||||
value = JsonValue::MakeObject();
|
||||
++mPosition;
|
||||
skipWhitespace();
|
||||
if (consume('}'))
|
||||
return true;
|
||||
|
||||
while (mPosition < mText.size())
|
||||
{
|
||||
std::string key;
|
||||
if (!parseString(key))
|
||||
return false;
|
||||
|
||||
skipWhitespace();
|
||||
if (!consume(':'))
|
||||
{
|
||||
setError("Expected ':' after JSON object key.");
|
||||
return false;
|
||||
}
|
||||
|
||||
skipWhitespace();
|
||||
JsonValue item;
|
||||
if (!parseValue(item))
|
||||
return false;
|
||||
|
||||
value.set(key, item);
|
||||
|
||||
skipWhitespace();
|
||||
if (consume('}'))
|
||||
return true;
|
||||
if (!consume(','))
|
||||
{
|
||||
setError("Expected ',' or '}' in JSON object.");
|
||||
return false;
|
||||
}
|
||||
skipWhitespace();
|
||||
}
|
||||
|
||||
setError("Unexpected end of JSON object.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parseArray(JsonValue& value)
|
||||
{
|
||||
value = JsonValue::MakeArray();
|
||||
++mPosition;
|
||||
skipWhitespace();
|
||||
if (consume(']'))
|
||||
return true;
|
||||
|
||||
while (mPosition < mText.size())
|
||||
{
|
||||
JsonValue item;
|
||||
if (!parseValue(item))
|
||||
return false;
|
||||
|
||||
value.pushBack(item);
|
||||
|
||||
skipWhitespace();
|
||||
if (consume(']'))
|
||||
return true;
|
||||
if (!consume(','))
|
||||
{
|
||||
setError("Expected ',' or ']' in JSON array.");
|
||||
return false;
|
||||
}
|
||||
skipWhitespace();
|
||||
}
|
||||
|
||||
setError("Unexpected end of JSON array.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parseString(std::string& value)
|
||||
{
|
||||
if (!consume('"'))
|
||||
{
|
||||
setError("Expected string literal.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostringstream result;
|
||||
while (mPosition < mText.size())
|
||||
{
|
||||
char ch = mText[mPosition++];
|
||||
if (ch == '"')
|
||||
{
|
||||
value = result.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ch == '\\')
|
||||
{
|
||||
if (mPosition >= mText.size())
|
||||
{
|
||||
setError("Unexpected end of escaped JSON string.");
|
||||
return false;
|
||||
}
|
||||
|
||||
char escaped = mText[mPosition++];
|
||||
switch (escaped)
|
||||
{
|
||||
case '"': result << '"'; break;
|
||||
case '\\': result << '\\'; break;
|
||||
case '/': result << '/'; break;
|
||||
case 'b': result << '\b'; break;
|
||||
case 'f': result << '\f'; break;
|
||||
case 'n': result << '\n'; break;
|
||||
case 'r': result << '\r'; break;
|
||||
case 't': result << '\t'; break;
|
||||
case 'u':
|
||||
if (!parseUnicodeEscape(result))
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
setError("Invalid escape sequence in JSON string.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (static_cast<unsigned char>(ch) < 0x20)
|
||||
{
|
||||
setError("Unescaped control character in JSON string.");
|
||||
return false;
|
||||
}
|
||||
result << ch;
|
||||
}
|
||||
}
|
||||
|
||||
setError("Unexpected end of JSON string.");
|
||||
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;
|
||||
|
||||
if (mText[mPosition] == '-')
|
||||
++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;
|
||||
}
|
||||
|
||||
if (mPosition < mText.size() && (mText[mPosition] == 'e' || mText[mPosition] == 'E'))
|
||||
{
|
||||
++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' || errno == ERANGE || !std::isfinite(parsed))
|
||||
{
|
||||
setError("Invalid JSON number.");
|
||||
return false;
|
||||
}
|
||||
|
||||
value = JsonValue(parsed);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseLiteral(const char* literal, const JsonValue& literalValue, JsonValue& value)
|
||||
{
|
||||
std::size_t length = strlen(literal);
|
||||
if (mText.compare(mPosition, length, literal) != 0)
|
||||
{
|
||||
setError("Invalid JSON literal.");
|
||||
return false;
|
||||
}
|
||||
|
||||
mPosition += length;
|
||||
value = literalValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
void skipWhitespace()
|
||||
{
|
||||
while (mPosition < mText.size() && std::isspace(static_cast<unsigned char>(mText[mPosition])))
|
||||
++mPosition;
|
||||
}
|
||||
|
||||
bool consume(char expected)
|
||||
{
|
||||
if (mPosition < mText.size() && mText[mPosition] == expected)
|
||||
{
|
||||
++mPosition;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setError(const std::string& error)
|
||||
{
|
||||
if (mError.empty())
|
||||
mError = error;
|
||||
}
|
||||
|
||||
const std::string& mText;
|
||||
std::string& mError;
|
||||
std::size_t mPosition;
|
||||
};
|
||||
|
||||
void SerializeJsonImpl(const JsonValue& value, std::ostringstream& output, bool pretty, int indentLevel)
|
||||
{
|
||||
auto indent = [&](int level) {
|
||||
if (!pretty)
|
||||
return;
|
||||
for (int i = 0; i < level; ++i)
|
||||
output << " ";
|
||||
};
|
||||
|
||||
switch (value.type())
|
||||
{
|
||||
case JsonValue::Type::Null:
|
||||
output << "null";
|
||||
break;
|
||||
case JsonValue::Type::Boolean:
|
||||
output << (value.asBoolean() ? "true" : "false");
|
||||
break;
|
||||
case JsonValue::Type::Number:
|
||||
{
|
||||
double number = value.asNumber();
|
||||
if (std::isfinite(number))
|
||||
{
|
||||
output << std::setprecision(15) << number;
|
||||
}
|
||||
else
|
||||
{
|
||||
output << "0";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JsonValue::Type::String:
|
||||
{
|
||||
output << '"';
|
||||
for (char ch : value.asString())
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case '"': output << "\\\""; break;
|
||||
case '\\': output << "\\\\"; break;
|
||||
case '\b': output << "\\b"; break;
|
||||
case '\f': output << "\\f"; break;
|
||||
case '\n': output << "\\n"; break;
|
||||
case '\r': output << "\\r"; break;
|
||||
case '\t': output << "\\t"; break;
|
||||
default:
|
||||
if (static_cast<unsigned char>(ch) < 0x20)
|
||||
AppendControlEscape(static_cast<unsigned char>(ch), output);
|
||||
else
|
||||
output << ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
output << '"';
|
||||
break;
|
||||
}
|
||||
case JsonValue::Type::Array:
|
||||
{
|
||||
output << "[";
|
||||
const std::vector<JsonValue>& array = value.asArray();
|
||||
if (!array.empty())
|
||||
{
|
||||
if (pretty)
|
||||
output << "\n";
|
||||
for (std::size_t i = 0; i < array.size(); ++i)
|
||||
{
|
||||
indent(indentLevel + 1);
|
||||
SerializeJsonImpl(array[i], output, pretty, indentLevel + 1);
|
||||
if (i + 1 != array.size())
|
||||
output << ",";
|
||||
if (pretty)
|
||||
output << "\n";
|
||||
}
|
||||
indent(indentLevel);
|
||||
}
|
||||
output << "]";
|
||||
break;
|
||||
}
|
||||
case JsonValue::Type::Object:
|
||||
{
|
||||
output << "{";
|
||||
const std::map<std::string, JsonValue>& object = value.asObject();
|
||||
if (!object.empty())
|
||||
{
|
||||
if (pretty)
|
||||
output << "\n";
|
||||
std::size_t index = 0;
|
||||
for (const auto& item : object)
|
||||
{
|
||||
indent(indentLevel + 1);
|
||||
SerializeJsonImpl(JsonValue(item.first), output, pretty, indentLevel + 1);
|
||||
output << (pretty ? ": " : ":");
|
||||
SerializeJsonImpl(item.second, output, pretty, indentLevel + 1);
|
||||
if (++index != object.size())
|
||||
output << ",";
|
||||
if (pretty)
|
||||
output << "\n";
|
||||
}
|
||||
indent(indentLevel);
|
||||
}
|
||||
output << "}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonValue::JsonValue()
|
||||
: mType(Type::Null), mBooleanValue(false), mNumberValue(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
JsonValue::JsonValue(bool value)
|
||||
: mType(Type::Boolean), mBooleanValue(value), mNumberValue(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
JsonValue::JsonValue(double value)
|
||||
: mType(Type::Number), mBooleanValue(false), mNumberValue(value)
|
||||
{
|
||||
}
|
||||
|
||||
JsonValue::JsonValue(const char* value)
|
||||
: mType(Type::String), mBooleanValue(false), mNumberValue(0.0), mStringValue(value ? value : "")
|
||||
{
|
||||
}
|
||||
|
||||
JsonValue::JsonValue(const std::string& value)
|
||||
: mType(Type::String), mBooleanValue(false), mNumberValue(0.0), mStringValue(value)
|
||||
{
|
||||
}
|
||||
|
||||
JsonValue JsonValue::MakeArray()
|
||||
{
|
||||
JsonValue value;
|
||||
value.reset(Type::Array);
|
||||
return value;
|
||||
}
|
||||
|
||||
JsonValue JsonValue::MakeObject()
|
||||
{
|
||||
JsonValue value;
|
||||
value.reset(Type::Object);
|
||||
return value;
|
||||
}
|
||||
|
||||
bool JsonValue::asBoolean(bool fallback) const
|
||||
{
|
||||
return mType == Type::Boolean ? mBooleanValue : fallback;
|
||||
}
|
||||
|
||||
double JsonValue::asNumber(double fallback) const
|
||||
{
|
||||
return mType == Type::Number ? mNumberValue : fallback;
|
||||
}
|
||||
|
||||
const std::string& JsonValue::asString() const
|
||||
{
|
||||
static const std::string emptyString;
|
||||
return mType == Type::String ? mStringValue : emptyString;
|
||||
}
|
||||
|
||||
const std::vector<JsonValue>& JsonValue::asArray() const
|
||||
{
|
||||
static const std::vector<JsonValue> emptyArray;
|
||||
return mType == Type::Array ? mArrayValue : emptyArray;
|
||||
}
|
||||
|
||||
const std::map<std::string, JsonValue>& JsonValue::asObject() const
|
||||
{
|
||||
static const std::map<std::string, JsonValue> emptyObject;
|
||||
return mType == Type::Object ? mObjectValue : emptyObject;
|
||||
}
|
||||
|
||||
std::vector<JsonValue>& JsonValue::array()
|
||||
{
|
||||
if (mType != Type::Array)
|
||||
reset(Type::Array);
|
||||
return mArrayValue;
|
||||
}
|
||||
|
||||
std::map<std::string, JsonValue>& JsonValue::object()
|
||||
{
|
||||
if (mType != Type::Object)
|
||||
reset(Type::Object);
|
||||
return mObjectValue;
|
||||
}
|
||||
|
||||
void JsonValue::pushBack(const JsonValue& value)
|
||||
{
|
||||
array().push_back(value);
|
||||
}
|
||||
|
||||
void JsonValue::set(const std::string& key, const JsonValue& value)
|
||||
{
|
||||
object()[key] = value;
|
||||
}
|
||||
|
||||
const JsonValue* JsonValue::find(const std::string& key) const
|
||||
{
|
||||
if (mType != Type::Object)
|
||||
return nullptr;
|
||||
|
||||
auto iterator = mObjectValue.find(key);
|
||||
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();
|
||||
JsonParser parser(text, error);
|
||||
return parser.parse(value);
|
||||
}
|
||||
|
||||
std::string SerializeJson(const JsonValue& value, bool pretty)
|
||||
{
|
||||
std::ostringstream output;
|
||||
SerializeJsonImpl(value, output, pretty, 0);
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::vector<double> JsonArrayToNumbers(const JsonValue& value)
|
||||
{
|
||||
std::vector<double> numbers;
|
||||
for (const JsonValue& item : value.asArray())
|
||||
{
|
||||
if (item.isNumber())
|
||||
numbers.push_back(item.asNumber());
|
||||
}
|
||||
return numbers;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class JsonValue
|
||||
{
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
Null,
|
||||
Boolean,
|
||||
Number,
|
||||
String,
|
||||
Array,
|
||||
Object
|
||||
};
|
||||
|
||||
JsonValue();
|
||||
explicit JsonValue(bool value);
|
||||
explicit JsonValue(double value);
|
||||
explicit JsonValue(const char* value);
|
||||
explicit JsonValue(const std::string& value);
|
||||
|
||||
static JsonValue MakeArray();
|
||||
static JsonValue MakeObject();
|
||||
|
||||
Type type() const { return mType; }
|
||||
|
||||
bool isNull() const { return mType == Type::Null; }
|
||||
bool isBoolean() const { return mType == Type::Boolean; }
|
||||
bool isNumber() const { return mType == Type::Number; }
|
||||
bool isString() const { return mType == Type::String; }
|
||||
bool isArray() const { return mType == Type::Array; }
|
||||
bool isObject() const { return mType == Type::Object; }
|
||||
|
||||
bool asBoolean(bool fallback = false) const;
|
||||
double asNumber(double fallback = 0.0) const;
|
||||
const std::string& asString() const;
|
||||
const std::vector<JsonValue>& asArray() const;
|
||||
const std::map<std::string, JsonValue>& asObject() const;
|
||||
|
||||
std::vector<JsonValue>& array();
|
||||
std::map<std::string, JsonValue>& object();
|
||||
|
||||
void pushBack(const JsonValue& value);
|
||||
void set(const std::string& key, const JsonValue& value);
|
||||
|
||||
const JsonValue* find(const std::string& key) const;
|
||||
|
||||
private:
|
||||
void reset(Type type);
|
||||
|
||||
Type mType;
|
||||
bool mBooleanValue;
|
||||
double mNumberValue;
|
||||
std::string mStringValue;
|
||||
std::vector<JsonValue> mArrayValue;
|
||||
std::map<std::string, JsonValue> mObjectValue;
|
||||
};
|
||||
|
||||
bool ParseJson(const std::string& text, JsonValue& value, std::string& error);
|
||||
std::string SerializeJson(const JsonValue& value, bool pretty = false);
|
||||
std::vector<double> JsonArrayToNumbers(const JsonValue& value);
|
||||
@@ -0,0 +1,196 @@
|
||||
#include "stdafx.h"
|
||||
#include "RuntimeParameterUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string TrimText(const std::string& text)
|
||||
{
|
||||
std::size_t start = 0;
|
||||
while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start])))
|
||||
++start;
|
||||
|
||||
std::size_t end = text.size();
|
||||
while (end > start && std::isspace(static_cast<unsigned char>(text[end - 1])))
|
||||
--end;
|
||||
|
||||
return text.substr(start, end - start);
|
||||
}
|
||||
|
||||
bool IsFiniteNumber(double value)
|
||||
{
|
||||
return std::isfinite(value) != 0;
|
||||
}
|
||||
|
||||
std::string NormalizeTextValue(const std::string& text, unsigned maxLength)
|
||||
{
|
||||
std::string normalized;
|
||||
normalized.reserve(std::min<std::size_t>(text.size(), maxLength));
|
||||
for (unsigned char ch : text)
|
||||
{
|
||||
if (ch < 32 || ch > 126)
|
||||
continue;
|
||||
if (normalized.size() >= maxLength)
|
||||
break;
|
||||
normalized.push_back(static_cast<char>(ch));
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
std::string MakeSafePresetFileStem(const std::string& presetName)
|
||||
{
|
||||
std::string trimmed = TrimText(presetName);
|
||||
std::string safe;
|
||||
safe.reserve(trimmed.size());
|
||||
|
||||
for (unsigned char ch : trimmed)
|
||||
{
|
||||
if (std::isalnum(ch))
|
||||
safe.push_back(static_cast<char>(std::tolower(ch)));
|
||||
else if (ch == ' ' || ch == '-' || ch == '_')
|
||||
{
|
||||
if (safe.empty() || safe.back() == '-')
|
||||
continue;
|
||||
safe.push_back('-');
|
||||
}
|
||||
}
|
||||
|
||||
while (!safe.empty() && safe.back() == '-')
|
||||
safe.pop_back();
|
||||
|
||||
return safe;
|
||||
}
|
||||
|
||||
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition)
|
||||
{
|
||||
ShaderParameterValue value;
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Float:
|
||||
value.numberValues = definition.defaultNumbers.empty() ? std::vector<double>{ 0.0 } : definition.defaultNumbers;
|
||||
break;
|
||||
case ShaderParameterType::Vec2:
|
||||
value.numberValues = definition.defaultNumbers.size() == 2 ? definition.defaultNumbers : std::vector<double>{ 0.0, 0.0 };
|
||||
break;
|
||||
case ShaderParameterType::Color:
|
||||
value.numberValues = definition.defaultNumbers.size() == 4 ? definition.defaultNumbers : std::vector<double>{ 1.0, 1.0, 1.0, 1.0 };
|
||||
break;
|
||||
case ShaderParameterType::Boolean:
|
||||
value.booleanValue = definition.defaultBoolean;
|
||||
break;
|
||||
case ShaderParameterType::Enum:
|
||||
value.enumValue = definition.defaultEnumValue;
|
||||
break;
|
||||
case ShaderParameterType::Text:
|
||||
value.textValue = NormalizeTextValue(definition.defaultTextValue, definition.maxLength);
|
||||
break;
|
||||
case ShaderParameterType::Trigger:
|
||||
value.numberValues = { 0.0, -1000000.0 };
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool NormalizeAndValidateParameterValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error)
|
||||
{
|
||||
normalizedValue = DefaultValueForDefinition(definition);
|
||||
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Float:
|
||||
{
|
||||
if (!value.isNumber())
|
||||
{
|
||||
error = "Expected numeric value for float parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
double number = value.asNumber();
|
||||
if (!IsFiniteNumber(number))
|
||||
{
|
||||
error = "Float parameter '" + definition.id + "' must be finite.";
|
||||
return false;
|
||||
}
|
||||
if (!definition.minNumbers.empty())
|
||||
number = std::max(number, definition.minNumbers.front());
|
||||
if (!definition.maxNumbers.empty())
|
||||
number = std::min(number, definition.maxNumbers.front());
|
||||
normalizedValue.numberValues = { number };
|
||||
return true;
|
||||
}
|
||||
case ShaderParameterType::Vec2:
|
||||
case ShaderParameterType::Color:
|
||||
{
|
||||
std::vector<double> numbers = JsonArrayToNumbers(value);
|
||||
const std::size_t expectedSize = definition.type == ShaderParameterType::Vec2 ? 2 : 4;
|
||||
if (numbers.size() != expectedSize)
|
||||
{
|
||||
error = "Expected array value of size " + std::to_string(expectedSize) + " for parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
for (std::size_t index = 0; index < numbers.size(); ++index)
|
||||
{
|
||||
if (!IsFiniteNumber(numbers[index]))
|
||||
{
|
||||
error = "Parameter '" + definition.id + "' contains a non-finite value.";
|
||||
return false;
|
||||
}
|
||||
if (index < definition.minNumbers.size())
|
||||
numbers[index] = std::max(numbers[index], definition.minNumbers[index]);
|
||||
if (index < definition.maxNumbers.size())
|
||||
numbers[index] = std::min(numbers[index], definition.maxNumbers[index]);
|
||||
}
|
||||
normalizedValue.numberValues = numbers;
|
||||
return true;
|
||||
}
|
||||
case ShaderParameterType::Boolean:
|
||||
if (!value.isBoolean())
|
||||
{
|
||||
error = "Expected boolean value for parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
normalizedValue.booleanValue = value.asBoolean();
|
||||
return true;
|
||||
case ShaderParameterType::Enum:
|
||||
{
|
||||
if (!value.isString())
|
||||
{
|
||||
error = "Expected string value for enum parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
const std::string selectedValue = value.asString();
|
||||
for (const ShaderParameterOption& option : definition.enumOptions)
|
||||
{
|
||||
if (option.value == selectedValue)
|
||||
{
|
||||
normalizedValue.enumValue = selectedValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
error = "Enum parameter '" + definition.id + "' received unsupported option '" + selectedValue + "'.";
|
||||
return false;
|
||||
}
|
||||
case ShaderParameterType::Text:
|
||||
if (!value.isString())
|
||||
{
|
||||
error = "Expected string value for text parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
normalizedValue.textValue = NormalizeTextValue(value.asString(), definition.maxLength);
|
||||
return true;
|
||||
case ShaderParameterType::Trigger:
|
||||
if (!value.isNumber() && !value.isBoolean())
|
||||
{
|
||||
error = "Expected numeric or boolean value for trigger parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
normalizedValue.numberValues = { value.isNumber() ? std::max(0.0, std::floor(value.asNumber())) : 0.0, -1000000.0 };
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
std::string MakeSafePresetFileStem(const std::string& presetName);
|
||||
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition);
|
||||
bool NormalizeAndValidateParameterValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error);
|
||||
Reference in New Issue
Block a user