You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CrySystem/Serialization/JSONIArchive.cpp

1526 lines
40 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include "CrySystem_precompiled.h"
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include "Serialization/ClassFactory.h"
#include "Serialization/STL.h"
#include "JSONIArchive.h"
#include "Serialization/BlackBox.h"
#include "MemoryReader.h"
#include "MemoryWriter.h"
#if 0
# define DEBUG_TRACE(fmt, ...) printf(fmt "\n", __VA_ARGS__)
# define DEBUG_TRACE_TOKENIZER(fmt, ...) printf(fmt "\n", __VA_ARGS__)
#else
# define DEBUG_TRACE(...)
# define DEBUG_TRACE_TOKENIZER(...)
#endif
namespace Serialization {
static char hexValueTable[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static void unescapeString(std::vector<char>& buf, string& out, const char* begin, const char* end)
{
if (begin >= end)
{
out.clear();
return;
}
// TODO: use stack string
buf.resize(end - begin);
char* ptr = &buf[0];
while (begin != end)
{
if (*begin != '\\')
{
*ptr = *begin;
++ptr;
}
else
{
++begin;
if (begin == end)
{
break;
}
switch (*begin)
{
case '0':
*ptr = '\0';
++ptr;
break;
case 't':
*ptr = '\t';
++ptr;
break;
case 'n':
*ptr = '\n';
++ptr;
break;
case 'r':
*ptr = '\r';
++ptr;
break;
case '\\':
*ptr = '\\';
++ptr;
break;
case '\"':
*ptr = '\"';
++ptr;
break;
case '\'':
*ptr = '\'';
++ptr;
break;
case 'x':
if (begin + 2 < end)
{
*ptr = (hexValueTable[int(begin[1])] << 4) + hexValueTable[int(begin[2])];
++ptr;
begin += 2;
break;
}
default:
*ptr = *begin;
++ptr;
break;
}
}
++begin;
}
buf.resize(ptr - &buf[0]);
if (!buf.empty())
{
out.assign(&buf[0], &buf[0] + buf.size());
}
else
{
out.clear();
}
}
// ---------------------------------------------------------------------------
class JSONTokenizer
{
public:
JSONTokenizer();
Token operator()(const char* text) const;
private:
inline bool isSpace(char c) const;
inline bool isWordPart(unsigned char c) const;
inline bool isComment(char c) const;
inline bool isQuoteOpen(int& quoteIndex, char c) const;
inline bool isQuoteClose(int quoteIndex, char c) const;
inline bool isQuote(char c) const;
};
JSONTokenizer::JSONTokenizer()
{
}
inline bool JSONTokenizer::isSpace(char c) const
{
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
inline bool JSONTokenizer::isComment(char c) const
{
return c == '#';
}
inline bool JSONTokenizer::isQuote(char c) const
{
return c == '\"';
}
static const char charTypes[256] = {
0 /* 0x00: */,
0 /* 0x01: */,
0 /* 0x02: */,
0 /* 0x03: */,
0 /* 0x04: */,
0 /* 0x05: */,
0 /* 0x06: */,
0 /* 0x07: */,
0 /* 0x08: */,
0 /* 0x09: \t */,
0 /* 0x0A: \n */,
0 /* 0x0B: */,
0 /* 0x0C: */,
0 /* 0x0D: */,
0 /* 0x0E: */,
0 /* 0x0F: */,
0 /* 0x10: */,
0 /* 0x11: */,
0 /* 0x12: */,
0 /* 0x13: */,
0 /* 0x14: */,
0 /* 0x15: */,
0 /* 0x16: */,
0 /* 0x17: */,
0 /* 0x18: */,
0 /* 0x19: */,
0 /* 0x1A: */,
0 /* 0x1B: */,
0 /* 0x1C: */,
0 /* 0x1D: */,
0 /* 0x1E: */,
0 /* 0x1F: */,
0 /* 0x20: */,
0 /* 0x21: ! */,
0 /* 0x22: " */,
0 /* 0x23: # */,
0 /* 0x24: $ */,
0 /* 0x25: % */,
0 /* 0x26: & */,
0 /* 0x27: ' */,
0 /* 0x28: ( */,
0 /* 0x29: ) */,
0 /* 0x2A: * */,
0 /* 0x2B: + */,
0 /* 0x2C: , */,
1 /* 0x2D: - */,
1 /* 0x2E: . */,
0 /* 0x2F: / */,
1 /* 0x30: 0 */,
1 /* 0x31: 1 */,
1 /* 0x32: 2 */,
1 /* 0x33: 3 */,
1 /* 0x34: 4 */,
1 /* 0x35: 5 */,
1 /* 0x36: 6 */,
1 /* 0x37: 7 */,
1 /* 0x38: 8 */,
1 /* 0x39: 9 */,
0 /* 0x3A: : */,
0 /* 0x3B: ; */,
0 /* 0x3C: < */,
0 /* 0x3D: = */,
0 /* 0x3E: > */,
0 /* 0x3F: ? */,
0 /* 0x40: @ */,
1 /* 0x41: A */,
1 /* 0x42: B */,
1 /* 0x43: C */,
1 /* 0x44: D */,
1 /* 0x45: E */,
1 /* 0x46: F */,
1 /* 0x47: G */,
1 /* 0x48: H */,
1 /* 0x49: I */,
1 /* 0x4A: J */,
1 /* 0x4B: K */,
1 /* 0x4C: L */,
1 /* 0x4D: M */,
1 /* 0x4E: N */,
1 /* 0x4F: O */,
1 /* 0x50: P */,
1 /* 0x51: Q */,
1 /* 0x52: R */,
1 /* 0x53: S */,
1 /* 0x54: T */,
1 /* 0x55: U */,
1 /* 0x56: V */,
1 /* 0x57: W */,
1 /* 0x58: X */,
1 /* 0x59: Y */,
1 /* 0x5A: Z */,
0 /* 0x5B: [ */,
0 /* 0x5C: \ */,
0 /* 0x5D: ] */,
0 /* 0x5E: ^ */,
1 /* 0x5F: _ */,
0 /* 0x60: ` */,
1 /* 0x61: a */,
1 /* 0x62: b */,
1 /* 0x63: c */,
1 /* 0x64: d */,
1 /* 0x65: e */,
1 /* 0x66: f */,
1 /* 0x67: g */,
1 /* 0x68: h */,
1 /* 0x69: i */,
1 /* 0x6A: j */,
1 /* 0x6B: k */,
1 /* 0x6C: l */,
1 /* 0x6D: m */,
1 /* 0x6E: n */,
1 /* 0x6F: o */,
1 /* 0x70: p */,
1 /* 0x71: q */,
1 /* 0x72: r */,
1 /* 0x73: s */,
1 /* 0x74: t */,
1 /* 0x75: u */,
1 /* 0x76: v */,
1 /* 0x77: w */,
1 /* 0x78: x */,
1 /* 0x79: y */,
1 /* 0x7A: z */,
0 /* 0x7B: { */,
0 /* 0x7C: | */,
0 /* 0x7D: } */,
0 /* 0x7E: ~ */,
0 /* 0x7F: */,
0 /* 0x80: */,
0 /* 0x81: */,
0 /* 0x82: */,
0 /* 0x83: */,
0 /* 0x84: */,
0 /* 0x85: */,
0 /* 0x86: */,
0 /* 0x87: */,
0 /* 0x88: */,
0 /* 0x89: */,
0 /* 0x8A: */,
0 /* 0x8B: */,
0 /* 0x8C: */,
0 /* 0x8D: */,
0 /* 0x8E: */,
0 /* 0x8F: */,
0 /* 0x90: */,
0 /* 0x91: */,
0 /* 0x92: */,
0 /* 0x93: */,
0 /* 0x94: */,
0 /* 0x95: */,
0 /* 0x96: */,
0 /* 0x97: */,
0 /* 0x98: */,
0 /* 0x99: */,
0 /* 0x9A: */,
0 /* 0x9B: */,
0 /* 0x9C: */,
0 /* 0x9D: */,
0 /* 0x9E: */,
0 /* 0x9F: */,
0 /* 0xA0: */,
0 /* 0xA1: */,
0 /* 0xA2: */,
0 /* 0xA3: */,
0 /* 0xA4: */,
0 /* 0xA5: */,
0 /* 0xA6: */,
0 /* 0xA7: */,
0 /* 0xA8: */,
0 /* 0xA9: */,
0 /* 0xAA: */,
0 /* 0xAB: */,
0 /* 0xAC: */,
0 /* 0xAD: */,
0 /* 0xAE: */,
0 /* 0xAF: */,
0 /* 0xB0: */,
0 /* 0xB1: */,
0 /* 0xB2: */,
0 /* 0xB3: */,
0 /* 0xB4: */,
0 /* 0xB5: */,
0 /* 0xB6: */,
0 /* 0xB7: */,
0 /* 0xB8: */,
0 /* 0xB9: */,
0 /* 0xBA: */,
0 /* 0xBB: */,
0 /* 0xBC: */,
0 /* 0xBD: */,
0 /* 0xBE: */,
0 /* 0xBF: */,
0 /* 0xC0: */,
0 /* 0xC1: */,
0 /* 0xC2: */,
0 /* 0xC3: */,
0 /* 0xC4: */,
0 /* 0xC5: */,
0 /* 0xC6: */,
0 /* 0xC7: */,
0 /* 0xC8: */,
0 /* 0xC9: */,
0 /* 0xCA: */,
0 /* 0xCB: */,
0 /* 0xCC: */,
0 /* 0xCD: */,
0 /* 0xCE: */,
0 /* 0xCF: */,
0 /* 0xD0: */,
0 /* 0xD1: */,
0 /* 0xD2: */,
0 /* 0xD3: */,
0 /* 0xD4: */,
0 /* 0xD5: */,
0 /* 0xD6: */,
0 /* 0xD7: */,
0 /* 0xD8: */,
0 /* 0xD9: */,
0 /* 0xDA: */,
0 /* 0xDB: */,
0 /* 0xDC: */,
0 /* 0xDD: */,
0 /* 0xDE: */,
0 /* 0xDF: */,
0 /* 0xE0: */,
0 /* 0xE1: */,
0 /* 0xE2: */,
0 /* 0xE3: */,
0 /* 0xE4: */,
0 /* 0xE5: */,
0 /* 0xE6: */,
0 /* 0xE7: */,
0 /* 0xE8: */,
0 /* 0xE9: */,
0 /* 0xEA: */,
0 /* 0xEB: */,
0 /* 0xEC: */,
0 /* 0xED: */,
0 /* 0xEE: */,
0 /* 0xEF: */,
0 /* 0xF0: */,
0 /* 0xF1: */,
0 /* 0xF2: */,
0 /* 0xF3: */,
0 /* 0xF4: */,
0 /* 0xF5: */,
0 /* 0xF6: */,
0 /* 0xF7: */,
0 /* 0xF8: */,
0 /* 0xF9: */,
0 /* 0xFA: */,
0 /* 0xFB: */,
0 /* 0xFC: */,
0 /* 0xFD: */,
0 /* 0xFE: */,
0 /* 0xFF: */
};
inline bool JSONTokenizer::isWordPart(unsigned char c) const
{
return charTypes[c] != 0;
}
Token JSONTokenizer::operator()(const char* ptr) const
{
while (isSpace(*ptr))
{
++ptr;
}
Token cur(ptr, ptr);
while (!cur && *ptr != '\0')
{
while (isComment(*cur.end))
{
#if 0
const char* commentStart = ptr;
#endif
while (*cur.end && *cur.end != '\n')
{
++cur.end;
}
while (isSpace(*cur.end))
{
++cur.end;
}
DEBUG_TRACE_TOKENIZER("Got comment: '%s'", string(commentStart, cur.end).c_str());
cur.start = cur.end;
}
CRY_ASSERT(!isSpace(*cur.end));
if (isQuote(*cur.end))
{
++cur.end;
while (*cur.end)
{
if (*cur.end == '\\')
{
++cur.end;
if (*cur.end)
{
if (*cur.end != 'x' && *cur.end != 'X')
{
++cur.end;
}
else
{
++cur.end;
if (*cur.end)
{
++cur.end;
}
}
}
}
if (isQuote(*cur.end))
{
++cur.end;
DEBUG_TRACE_TOKENIZER("Tokenizer result: '%s'", cur.str().c_str());
return cur;
}
else
{
++cur.end;
}
}
}
else
{
if (!*cur.end)
{
return cur;
}
DEBUG_TRACE_TOKENIZER("%c", *cur.end);
if (isWordPart(*cur.end))
{
do
{
++cur.end;
} while (isWordPart(*cur.end) != 0);
}
else
{
++cur.end;
return cur;
}
DEBUG_TRACE_TOKENIZER("Tokenizer result: '%s'", cur.str().c_str());
return cur;
}
}
DEBUG_TRACE_TOKENIZER("Tokenizer result: '%s'", cur.str().c_str());
return cur;
}
// ---------------------------------------------------------------------------
JSONIArchive::JSONIArchive()
: IArchive(INPUT | TEXT)
, buffer_(0)
{
}
JSONIArchive::~JSONIArchive()
{
if (buffer_)
{
free(buffer_);
buffer_ = 0;
}
stack_.clear();
reader_.reset();
}
bool JSONIArchive::open(const char* buffer, size_t length, bool free)
{
if (!length)
{
return false;
}
if (buffer)
{
reader_.reset(new MemoryReader(buffer, length, free));
}
buffer_ = 0;
token_ = Token(reader_->begin(), reader_->begin());
stack_.clear();
stack_.push_back(Level());
readToken();
putToken();
stack_.back().start = token_.end;
return true;
}
bool JSONIArchive::load(const char* filename)
{
FILE* file = nullptr;
azfopen(&file, filename, "rb");
if (file)
{
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
void* buffer = 0;
if (fileSize > 0)
{
buffer = CryModuleMalloc(fileSize + 1);
CRY_ASSERT(buffer != 0);
memset(buffer, 0, fileSize + 1);
size_t elementsRead = fread(buffer, fileSize, 1, file);
CRY_ASSERT(((char*)(buffer))[fileSize] == '\0');
if (elementsRead != 1)
{
CryModuleFree(buffer);
return false;
}
}
fclose(file);
filename_ = filename;
buffer_ = buffer;
if (fileSize > 0)
{
return open((char*)buffer, fileSize, false);
}
else
{
return false;
}
}
else
{
return false;
}
}
void JSONIArchive::readToken()
{
JSONTokenizer tokenizer;
token_ = tokenizer(token_.end);
DEBUG_TRACE(" ~ read token '%s' at %i", token_.str().c_str(), token_.start - reader_->begin());
}
void JSONIArchive::putToken()
{
DEBUG_TRACE(" putToken: '%s'", token_.str().c_str());
token_ = Token(token_.start, token_.start);
}
int JSONIArchive::line(const char* position) const
{
return int(std::count(reader_->begin(), position, '\n') + 1);
}
bool JSONIArchive::isName(Token token) const
{
if (!token)
{
return false;
}
char firstChar = token.start[0];
if (firstChar == '"')
{
return true;
}
return false;
}
bool JSONIArchive::expect(char token)
{
if (token_ != token)
{
const char* lineEnd = token_.start;
while (lineEnd && *lineEnd != '\0' && *lineEnd != '\r' && *lineEnd != '\n')
{
++lineEnd;
}
MemoryWriter msg;
msg << "Error parsing file, expected ':' at line " << line(token_.start) << ":\n"
<< string(token_.start, lineEnd).c_str();
CRY_ASSERT_MESSAGE(0, msg.c_str());
return false;
}
return true;
}
void JSONIArchive::skipBlock()
{
DEBUG_TRACE("Skipping block from %i ...", token_.end - reader_->begin());
if (openBracket() || openContainerBracket())
{
closeBracket(); // Skipping entire block
}
else
{
readToken(); // Skipping value
}
readToken();
if (token_ != ',')
{
putToken();
}
DEBUG_TRACE(" ...till %i", token_.end - reader_->begin());
}
bool JSONIArchive::findName(const char* name, Token* outName)
{
DEBUG_TRACE(" * finding name '%s'", name);
DEBUG_TRACE(" started at byte %i", int(token_.start - reader_->begin()));
if (stack_.empty())
{
// TODO: diagnose
return false;
}
if (stack_.back().isKeyValue)
{
return true;
}
const char* start = 0;
const char* blockBegin = stack_.back().start;
if (*blockBegin == '\0')
{
return false;
}
readToken();
if (token_ == ',')
{
readToken();
}
if (!token_)
{
start = blockBegin;
token_.set(blockBegin, blockBegin);
readToken();
}
if (stack_.size() == 1 || stack_.back().isContainer || outName != 0)
{
if (token_ == ']' || token_ == '}')
{
DEBUG_TRACE("Got close bracket...");
putToken();
return false;
}
else
{
DEBUG_TRACE("Got unnamed value: '%s'", token_.str().c_str());
putToken();
return true;
}
}
else
{
if (isName(token_))
{
DEBUG_TRACE("Seems to be a name '%s'", token_.str().c_str());
Token nameContent(token_.start + 1, token_.end - 1);
if (nameContent == name)
{
readToken();
expect(':');
DEBUG_TRACE("Got one");
return true;
}
else
{
start = token_.start;
readToken();
expect(':');
skipBlock();
}
}
else
{
start = token_.start;
if (token_ == ']' || token_ == '}')
{
token_ = Token(blockBegin, blockBegin);
}
else
{
putToken();
skipBlock();
}
}
}
while (true)
{
readToken();
if (!token_)
{
token_.set(blockBegin, blockBegin);
continue;
}
//return false; // Reached end of file while searching for name
DEBUG_TRACE("'%s'", token_.str().c_str());
DEBUG_TRACE("Checking for loop: %i and %i", token_.start - reader_->begin(), start - reader_->begin());
CRY_ASSERT(start != 0);
if (token_.start == start)
{
putToken();
DEBUG_TRACE("unable to find...");
return false; // Reached a full circle: unable to find name
}
if (token_ == '}' || token_ == ']') // CONVERSION
{
DEBUG_TRACE("Going to begin of block, from %i", token_.start - reader_->begin());
token_ = Token(blockBegin, blockBegin);
DEBUG_TRACE(" to %i", token_.start - reader_->begin());
continue; // Reached '}' or ']' while searching for name, continue from begin of block
}
if (name[0] == '\0')
{
if (isName(token_))
{
readToken();
if (!token_)
{
return false; // Reached end of file while searching for name
}
expect(':');
skipBlock();
}
else
{
putToken(); // Not a name - put it back
return true;
}
}
else
{
if (isName(token_))
{
Token nameContent(token_.start + 1, token_.end - 1);
readToken();
expect(':');
if (nameContent == name)
{
return true;
}
else
{
skipBlock();
}
}
else
{
putToken();
skipBlock();
}
}
}
return false;
}
bool JSONIArchive::openBracket()
{
readToken();
if (token_ == '{')
{
return true;
}
putToken();
return false;
}
bool JSONIArchive::closeBracket()
{
int relativeLevel = 0;
while (true)
{
readToken();
if (token_ == ',')
{
readToken();
}
if (!token_)
{
MemoryWriter msg;
CRY_ASSERT(!stack_.empty());
const char* start = stack_.back().start;
msg << filename_.c_str() << ": " << line(start) << " line";
msg << ": End of file while no matching bracket found";
CRY_ASSERT_MESSAGE(0, msg.c_str());
return false;
}
else if (token_ == '}' || token_ == ']') // CONVERSION
{
if (relativeLevel == 0)
{
return true;
}
else
{
--relativeLevel;
}
}
else if (token_ == '{' || token_ == '[') // CONVERSION
{
++relativeLevel;
}
}
return false;
}
bool JSONIArchive::openContainerBracket()
{
readToken();
if (token_ == '[')
{
return true;
}
putToken();
return false;
}
bool JSONIArchive::closeContainerBracket()
{
readToken();
if (token_ == ']')
{
DEBUG_TRACE("closeContainerBracket(): ok");
return true;
}
else
{
DEBUG_TRACE("closeContainerBracket(): failed ('%s')", token_.str().c_str());
putToken();
return false;
}
}
bool JSONIArchive::operator()(const SStruct& ser, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
if (openBracket())
{
stack_.push_back(Level());
stack_.back().start = token_.end;
}
else if (openContainerBracket())
{
stack_.push_back(Level());
stack_.back().start = token_.end;
stack_.back().isContainer = true;
}
else
{
return false;
}
ser(*this);
CRY_ASSERT(!stack_.empty());
stack_.pop_back();
#if !defined(NDEBUG)
bool closed =
#endif
closeBracket();
CRY_ASSERT(closed);
return true;
}
return false;
}
bool JSONIArchive::operator()(const SBlackBox& box, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
if (openBracket() || openContainerBracket())
{
const char* start = token_.start;
putToken();
skipBlock();
const char* end = token_.start;
if (end < start)
{
CRY_ASSERT(0);
return false;
}
while (end > start &&
(*(end - 1) == ' '
|| *(end - 1) == '\r'
|| *(end - 1) == '\n'
|| *(end - 1) == '\t'))
{
--end;
}
// box has to be const in the interface so we can serialize
// temporary variables (i.e. function call result or structures
// constructed on the stack)
const_cast<SBlackBox&>(box).set("json", (void*)start, end - start);
return true;
}
}
return false;
}
bool JSONIArchive::operator()(IKeyValue& keyValue, [[maybe_unused]] const char* name, [[maybe_unused]] const char* label)
{
Token nextName;
if (!stack_.empty() && stack_.back().isContainer)
{
readToken();
if (isName(token_) && checkStringValueToken())
{
string key;
unescapeString(unescapeBuffer_, key, token_.start + 1, token_.end - 1);
keyValue.set(key.c_str());
readToken();
if (!expect(':'))
{
return false;
}
if (!keyValue.serializeValue(*this, "", 0))
{
return false;
}
return true;
}
else
{
putToken();
return false;
}
}
else if (findName("", &nextName))
{
string key;
unescapeString(unescapeBuffer_, key, nextName.start + 1, nextName.end - 1);
keyValue.set(key.c_str());
stack_.push_back(Level());
stack_.back().isKeyValue = true;
bool result = keyValue.serializeValue(*this, "", 0);
if (stack_.empty())
{
// TODO: diagnose
return false;
}
stack_.pop_back();
return result;
}
return false;
}
bool JSONIArchive::operator()(IPointer& ser, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
if (openBracket())
{
stack_.push_back(Level());
stack_.back().start = token_.end;
stack_.back().isKeyValue = true;
readToken();
if (isName(token_))
{
if (checkStringValueToken())
{
string typeName;
unescapeString(unescapeBuffer_, typeName, token_.start + 1, token_.end - 1);
if (typeName != ser.registeredTypeName())
{
ser.create(typeName.c_str());
}
readToken();
expect(':');
operator()(ser.serializer(), "", 0);
}
}
else
{
putToken();
ser.create("");
}
closeBracket();
stack_.pop_back();
return true;
}
}
return false;
}
bool JSONIArchive::operator()(IContainer& ser, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
bool containerBracket = openContainerBracket();
bool dictionaryBracket = false;
if (!containerBracket)
{
dictionaryBracket = openBracket();
}
if (containerBracket || dictionaryBracket)
{
stack_.push_back(Level());
stack_.back().isContainer = true;
stack_.back().start = token_.end;
std::size_t size = ser.size();
std::size_t index = 0;
while (true)
{
readToken();
if (token_ == ',')
{
readToken();
}
if (token_ == '}' || token_ == ']')
{
break;
}
else if (!token_)
{
CRY_ASSERT(0 && "Reached end of file while reading container!");
return false;
}
putToken();
if (index == size)
{
size = index + 1;
}
if (index < size)
{
if (!ser(*this, "", ""))
{
// We've got a named item within a container,
// i.e. looks like a dictionary but not a container.
// Bail out, it is nothing we can do here.
closeBracket();
break;
}
}
else
{
skipBlock();
}
ser.next();
++index;
}
if (size > index)
{
ser.resize(index);
}
CRY_ASSERT(!stack_.empty());
stack_.pop_back();
return true;
}
}
return false;
}
void JSONIArchive::checkValueToken()
{
if (!token_)
{
CRY_ASSERT(!stack_.empty());
MemoryWriter msg;
const char* start = stack_.back().start;
msg << filename_.c_str() << ": " << line(start) << " line";
msg << ": End of file while reading element's value";
CRY_ASSERT_MESSAGE(0, msg.c_str());
}
}
bool JSONIArchive::checkStringValueToken()
{
if (!token_)
{
return false;
MemoryWriter msg;
const char* start = stack_.back().start;
msg << filename_.c_str() << ": " << line(start) << " line";
msg << ": End of file while reading element's value";
CRY_ASSERT_MESSAGE(0, msg.c_str());
return false;
}
if (token_.start[0] != '"' || token_.end[-1] != '"')
{
return false;
MemoryWriter msg;
const char* start = stack_.back().start;
msg << filename_.c_str() << ": " << line(start) << " line";
msg << ": Expected string";
CRY_ASSERT_MESSAGE(0, msg.c_str());
return false;
}
return true;
}
bool JSONIArchive::operator()(int32& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
value = strtol(token_.start, 0, 10);
return true;
}
return false;
}
bool JSONIArchive::operator()(uint32& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
value = strtoul(token_.start, 0, 10);
return true;
}
return false;
}
bool JSONIArchive::operator()(int16& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
value = (int16)strtol(token_.start, 0, 10);
return true;
}
return false;
}
bool JSONIArchive::operator()(uint16& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
value = (uint16)strtoul(token_.start, 0, 10);
return true;
}
return false;
}
bool JSONIArchive::operator()(int64& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
#ifdef _MSC_VER
value = _strtoi64(token_.start, 0, 10);
#else
value = strtoll(token_.start, 0, 10);
#endif
return true;
}
return false;
}
bool JSONIArchive::operator()(uint64& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
#ifdef _MSC_VER
value = _strtoui64(token_.start, 0, 10);
#else
value = strtoull(token_.start, 0, 10);
#endif
return true;
}
return false;
}
bool JSONIArchive::operator()(float& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
#ifdef _MSC_VER
value = float(std::atof(token_.str().c_str()));
#else
value = strtof(token_.start, 0);
#endif
return true;
}
return false;
}
bool JSONIArchive::operator()(double& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
#ifdef _MSC_VER
value = std::atof(token_.str().c_str());
#else
value = strtod(token_.start, 0);
#endif
return true;
}
return false;
}
bool JSONIArchive::operator()(IString& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
if (checkStringValueToken())
{
string buf;
unescapeString(unescapeBuffer_, buf, token_.start + 1, token_.end - 1);
value.set(buf.c_str());
}
else
{
return false;
}
return true;
}
return false;
}
inline size_t utf8InUtf16Len(const char* p)
{
size_t result = 0;
for (; *p; ++p)
{
unsigned char ch = (unsigned char)(*p);
if (ch < 0x80 || (ch >= 0xC0 && ch < 0xFC))
{
++result;
}
}
return result;
}
inline const char* readUtf16FromUtf8(unsigned int* ch, const char* s)
{
const unsigned char byteMark = 0x80;
const unsigned char byteMaskRead = 0x3F;
const unsigned char* str = (const unsigned char*)s;
size_t len;
if (*str < byteMark)
{
*ch = *str;
return s + 1;
}
else if (*str < 0xC0)
{
*ch = ' ';
return s + 1;
}
else if (*str < 0xE0)
{
len = 2;
}
else if (*str < 0xF0)
{
len = 3;
}
else if (*str < 0xF8)
{
len = 4;
}
else if (*str < 0xFC)
{
len = 5;
}
else
{
*ch = ' ';
return s + 1;
}
const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
*ch = (*str++ & ~firstByteMark[len]);
switch (len)
{
case 5:
(*ch) <<= 6;
(*ch) += (*str++ & byteMaskRead);
case 4:
(*ch) <<= 6;
(*ch) += (*str++ & byteMaskRead);
case 3:
(*ch) <<= 6;
(*ch) += (*str++ & byteMaskRead);
case 2:
(*ch) <<= 6;
(*ch) += (*str++ & byteMaskRead);
}
return (const char*)str;
}
inline void utf8ToUtf16(wstring* out, const char* in)
{
out->clear();
out->reserve(utf8InUtf16Len(in));
for (; *in; )
{
unsigned int character;
in = readUtf16FromUtf8(&character, in);
(*out) += (wchar_t)character;
}
}
bool JSONIArchive::operator()(IWString& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
if (checkStringValueToken())
{
string buf;
unescapeString(unescapeBuffer_, buf, token_.start + 1, token_.end - 1);
wstring wbuf;
utf8ToUtf16(&wbuf, buf.c_str());
value.set(wbuf.c_str());
}
else
{
return false;
}
return true;
}
return false;
}
bool JSONIArchive::operator()(bool& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
if (token_ == "true")
{
value = true;
}
else if (token_ == "false")
{
value = false;
}
else
{
CRY_ASSERT(0 && "Invalid boolean value");
}
return true;
}
return false;
}
bool JSONIArchive::operator()(int8& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
value = (int8)strtol(token_.start, 0, 10);
return true;
}
return false;
}
bool JSONIArchive::operator()(uint8& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
value = (uint8)strtol(token_.start, 0, 10);
return true;
}
return false;
}
bool JSONIArchive::operator()(char& value, const char* name, [[maybe_unused]] const char* label)
{
if (findName(name))
{
readToken();
checkValueToken();
value = (char)strtol(token_.start, 0, 10);
return true;
}
return false;
}
}
// vim:ts=4 sw=4: