1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-15 13:33:36 +02:00

Partial support for json5

This commit is contained in:
Ivan Savenko 2024-02-13 19:07:51 +02:00
parent 41493d6f67
commit 2632ab04f5
3 changed files with 111 additions and 25 deletions

View File

@ -23,10 +23,10 @@ struct JsonParsingSettings
{
JSON, // strict implementation of json format
JSONC, // json format that also allows comments that start from '//'
//JSON5 // TODO?
JSON5 // Partial support of 'json5' format
};
JsonFormatMode mode = JsonFormatMode::JSONC;
JsonFormatMode mode = JsonFormatMode::JSON5;
/// Maximum depth of elements
uint32_t maxDepth = 30;

View File

@ -19,6 +19,7 @@ VCMI_LIB_NAMESPACE_BEGIN
JsonParser::JsonParser(const char * inputString, size_t stringSize, const JsonParsingSettings & settings):
input(inputString, stringSize),
settings(settings),
currentDepth(0),
lineCount(1),
lineStart(0),
pos(0)
@ -29,13 +30,13 @@ JsonNode JsonParser::parse(const std::string & fileName)
{
JsonNode root;
if (input.size() == 0)
if (input.empty())
{
error("File is empty", false);
}
else
{
if (!TextOperations::isValidUnicodeString(&input[0], input.size()))
if (!TextOperations::isValidUnicodeString(input.data(), input.size()))
error("Not a valid UTF-8 file", false);
extractValue(root);
@ -96,6 +97,9 @@ bool JsonParser::extractValue(JsonNode &node)
bool JsonParser::extractWhitespace(bool verbose)
{
//TODO: JSON5 - C-style multi-line comments
//TODO: JSON5 - Additional white space characters are allowed
while (true)
{
while(pos < input.size() && static_cast<ui8>(input[pos]) <= ' ')
@ -133,6 +137,9 @@ bool JsonParser::extractWhitespace(bool verbose)
bool JsonParser::extractEscaping(std::string &str)
{
// TODO: support unicode escaping:
// \u1234
switch(input[pos])
{
break; case '\"': str += '\"';
@ -150,15 +157,27 @@ bool JsonParser::extractEscaping(std::string &str)
bool JsonParser::extractString(std::string &str)
{
if (input[pos] != '\"')
return error("String expected!");
//TODO: JSON5 - line breaks escaping
if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
{
if (input[pos] != '\"')
return error("String expected!");
}
else
{
if (input[pos] != '\"' && input[pos] != '\'')
return error("String expected!");
}
char lineTerminator = input[pos];
pos++;
size_t first = pos;
while (pos != input.size())
{
if (input[pos] == '\"') // Correct end of string
if (input[pos] == lineTerminator) // Correct end of string
{
str.append( &input[first], pos-first);
pos++;
@ -200,23 +219,43 @@ bool JsonParser::extractString(JsonNode &node)
return true;
}
bool JsonParser::extractLiteral(const std::string &literal)
bool JsonParser::extractLiteral(std::string & literal)
{
if (literal.compare(0, literal.size(), &input[pos], literal.size()) != 0)
while (pos < input.size() )
{
while (pos < input.size() && ((input[pos]>'a' && input[pos]<'z')
|| (input[pos]>'A' && input[pos]<'Z')))
pos++;
return error("Unknown literal found", true);
bool isUpperCase = input[pos]>='A' && input[pos]<='Z';
bool isLowerCase = input[pos]>='a' && input[pos]<='z';
bool isNumber = input[pos]>='0' && input[pos]<='9';
if (!isUpperCase && !isLowerCase && !isNumber)
break;
literal += input[pos];
pos++;
}
pos += literal.size();
return true;
}
bool JsonParser::extractAndCompareLiteral(const std::string &expectedLiteral)
{
std::string literal;
if (!extractLiteral(literal))
return false;
if (literal != expectedLiteral)
{
return error("Expected " + expectedLiteral + ", but unknown literal found", true);
return false;
}
return true;
}
bool JsonParser::extractNull(JsonNode &node)
{
if (!extractLiteral("null"))
if (!extractAndCompareLiteral("null"))
return false;
node.clear();
@ -225,7 +264,7 @@ bool JsonParser::extractNull(JsonNode &node)
bool JsonParser::extractTrue(JsonNode &node)
{
if (!extractLiteral("true"))
if (!extractAndCompareLiteral("true"))
return false;
node.Bool() = true;
@ -234,7 +273,7 @@ bool JsonParser::extractTrue(JsonNode &node)
bool JsonParser::extractFalse(JsonNode &node)
{
if (!extractLiteral("false"))
if (!extractAndCompareLiteral("false"))
return false;
node.Bool() = false;
@ -244,6 +283,11 @@ bool JsonParser::extractFalse(JsonNode &node)
bool JsonParser::extractStruct(JsonNode &node)
{
node.setType(JsonNode::JsonType::DATA_STRUCT);
if (currentDepth > settings.maxDepth)
error("Macimum allowed depth of json structure has been reached", true);
currentDepth++;
pos++;
if (!extractWhitespace())
@ -263,8 +307,25 @@ bool JsonParser::extractStruct(JsonNode &node)
bool overrideFlag = false;
std::string key;
if (!extractString(key))
return false;
if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
{
if (!extractString(key))
return false;
}
else
{
if (input[pos] == '\'' || input[pos] == '\"')
{
if (!extractString(key))
return false;
}
else
{
if (!extractLiteral(key))
return false;
}
}
if (key.find('#') != std::string::npos)
{
@ -304,6 +365,10 @@ bool JsonParser::extractStruct(JsonNode &node)
bool JsonParser::extractArray(JsonNode &node)
{
if (currentDepth > settings.maxDepth)
error("Macimum allowed depth of json structure has been reached", true);
currentDepth++;
pos++;
node.setType(JsonNode::JsonType::DATA_VECTOR);
@ -353,7 +418,10 @@ bool JsonParser::extractElement(JsonNode &node, char terminator)
if (input[pos] == terminator)
{
if (comma)
error("Extra comma found!", true);
{
if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
error("Extra comma found!", true);
}
return true;
}
@ -365,20 +433,32 @@ bool JsonParser::extractElement(JsonNode &node, char terminator)
bool JsonParser::extractFloat(JsonNode &node)
{
//TODO: JSON5 - hexacedimal support
//TODO: JSON5 - Numbers may be IEEE 754 positive infinity, negative infinity, and NaN (why?)
assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9'));
bool negative=false;
double result=0;
si64 integerPart = 0;
bool isFloat = false;
if (input[pos] == '-')
if (input[pos] == '+')
{
if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
error("Positive numbers should not have plus sign!", true);
pos++;
}
else if (input[pos] == '-')
{
pos++;
negative = true;
}
if (input[pos] < '0' || input[pos] > '9')
return error("Number expected!");
{
if (input[pos] != '.' && settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
return error("Number expected!");
}
//Extract integer part
while (input[pos] >= '0' && input[pos] <= '9')
@ -395,8 +475,12 @@ bool JsonParser::extractFloat(JsonNode &node)
isFloat = true;
pos++;
double fractMult = 0.1;
if (input[pos] < '0' || input[pos] > '9')
return error("Decimal part expected!");
if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
{
if (input[pos] < '0' || input[pos] > '9')
return error("Decimal part expected!");
}
while (input[pos] >= '0' && input[pos] <= '9')
{

View File

@ -20,13 +20,15 @@ class JsonParser
std::string errors; // Contains description of all encountered errors
std::string_view input; // Input data
ui32 lineCount; // Currently parsed line, starting from 1
uint32_t lineCount; // Currently parsed line, starting from 1
uint32_t currentDepth;
size_t lineStart; // Position of current line start
size_t pos; // Current position of parser
//Helpers
bool extractEscaping(std::string &str);
bool extractLiteral(const std::string &literal);
bool extractLiteral(std::string &literal);
bool extractAndCompareLiteral(const std::string &expectedLiteral);
bool extractString(std::string &string);
bool extractWhitespace(bool verbose = true);
bool extractSeparator();