mirror of
https://github.com/iaddis/metalnes.git
synced 2025-04-02 10:31:52 -04:00
680 lines
14 KiB
C++
680 lines
14 KiB
C++
// It's custom because why not. Need to capture comments and support trailing commas.
|
|
|
|
#pragma once
|
|
|
|
#include <stdint.h>
|
|
#include <string>
|
|
#include <functional>
|
|
#include <assert.h>
|
|
|
|
namespace Core {
|
|
|
|
|
|
enum class JsonToken
|
|
{
|
|
NONE,
|
|
OBJECT_BEGIN,
|
|
OBJECT_END,
|
|
ARRAY_BEGIN,
|
|
ARRAY_END,
|
|
COLON,
|
|
SEMICOLON,
|
|
EQUALS,
|
|
COMMA,
|
|
STRING,
|
|
BOOLEAN,
|
|
NUMBER,
|
|
BOOLEAN_TRUE,
|
|
BOOLEAN_FALSE,
|
|
END_OF_FILE
|
|
};
|
|
|
|
|
|
class JsonTokenizer
|
|
{
|
|
public:
|
|
|
|
JsonTokenizer(const std::string &str, const std::string &path)
|
|
:_str(str), _path(path)
|
|
{
|
|
_ptr = _str.c_str();
|
|
NextToken();
|
|
}
|
|
|
|
|
|
bool HasError() const {return _error;}
|
|
bool EndOfFile() const { return _token == JsonToken::END_OF_FILE;}
|
|
JsonToken Token() const {return _token;}
|
|
const std::string & TokenValue() const {return _token_value;}
|
|
const std::string & Comments() const {return _comments;}
|
|
|
|
JsonToken NextToken()
|
|
{
|
|
_token = ScanNextToken();
|
|
|
|
if (_verbose)
|
|
printf("Token %d %s\n", (int)_token, _token_value.c_str());
|
|
return _token;
|
|
}
|
|
|
|
bool Consume(JsonToken token)
|
|
{
|
|
if (_token != token) {
|
|
SetError("Expected token");
|
|
return false;
|
|
}
|
|
NextToken();
|
|
return true;
|
|
}
|
|
|
|
bool Expect(JsonToken token)
|
|
{
|
|
if (_token != token) {
|
|
SetError("Expected token");
|
|
assert (0);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ReadString(std::string &v)
|
|
{
|
|
if (!Expect(JsonToken::STRING)) {
|
|
v.clear();
|
|
return false;
|
|
|
|
}
|
|
v = TokenValue();
|
|
NextToken();
|
|
return true;
|
|
}
|
|
|
|
std::string ReadString() {
|
|
std::string s;
|
|
ReadString(s);
|
|
return s;
|
|
}
|
|
|
|
bool ReadNumber(long &v) {
|
|
if (!Expect(JsonToken::NUMBER)) {
|
|
v = 0;
|
|
return false;
|
|
}
|
|
|
|
v = strtol( TokenValue().c_str(), nullptr, 10);
|
|
NextToken();
|
|
return true;
|
|
}
|
|
|
|
bool ReadNumber(int &v) {
|
|
if (!Expect(JsonToken::NUMBER)) {
|
|
v = 0;
|
|
return false;
|
|
}
|
|
|
|
v = (int)strtol( TokenValue().c_str(), nullptr, 10);
|
|
NextToken();
|
|
return true;
|
|
}
|
|
|
|
bool ReadNumber(float &v) {
|
|
if (!Expect(JsonToken::NUMBER)) {
|
|
v = 0;
|
|
return false;
|
|
}
|
|
|
|
v = strtof( TokenValue().c_str(), nullptr);
|
|
NextToken();
|
|
return true;
|
|
}
|
|
|
|
bool ReadNumber(double &v) {
|
|
if (!Expect(JsonToken::NUMBER)) {
|
|
v = 0;
|
|
return false;
|
|
}
|
|
|
|
v = strtod( TokenValue().c_str(), nullptr);
|
|
NextToken();
|
|
return true;
|
|
}
|
|
|
|
// int ReadNumber() {
|
|
// int v;
|
|
// ReadNumber(v);
|
|
// return v;
|
|
// }
|
|
|
|
void SetError(const std::string &msg)
|
|
{
|
|
_error = true;
|
|
|
|
// add path/line/column
|
|
std::string pos = _path;
|
|
pos += '(';
|
|
pos += std::to_string(_line);
|
|
pos += ',';
|
|
pos += std::to_string(_column);
|
|
pos += ')';
|
|
_error_message = pos + ": " + msg;
|
|
}
|
|
|
|
const std::string &GetErrorMessage() const
|
|
{
|
|
return _error_message;
|
|
}
|
|
|
|
protected:
|
|
|
|
char Peek()
|
|
{
|
|
return *_ptr;
|
|
}
|
|
|
|
char ReadChar()
|
|
{
|
|
char c = *_ptr;
|
|
if (!c) return 0;
|
|
_ptr++;
|
|
_column++;
|
|
if (c == '\n')
|
|
{
|
|
_column = 1;
|
|
_line++;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
bool IsStringChar(char c)
|
|
{
|
|
return isalnum(c) || c == '_';
|
|
}
|
|
|
|
JsonToken ScanNextToken()
|
|
{
|
|
_token_value.clear();
|
|
_comments.clear();
|
|
|
|
for (;;)
|
|
{
|
|
char c = ReadChar();
|
|
if (!c) {
|
|
return JsonToken::END_OF_FILE;
|
|
}
|
|
|
|
if (isspace(c))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// handle "//" comments
|
|
if (c == '/')
|
|
{
|
|
SkipComments();
|
|
continue;
|
|
}
|
|
|
|
if (c == '"' || c == '\'')
|
|
{
|
|
while (Peek() != c)
|
|
{
|
|
_token_value += ReadChar();
|
|
}
|
|
ReadChar();
|
|
return JsonToken::STRING;
|
|
}
|
|
|
|
if (c == '-' || std::isdigit(c))
|
|
{
|
|
_token_value += c;
|
|
|
|
while (std::isdigit(Peek()) || Peek() == '.')
|
|
{
|
|
_token_value += ReadChar();
|
|
}
|
|
return JsonToken::NUMBER;
|
|
}
|
|
|
|
if (IsStringChar(c))
|
|
{
|
|
_token_value += c;
|
|
while (IsStringChar(Peek()))
|
|
{
|
|
_token_value += ReadChar();
|
|
}
|
|
if (c == 'f' && _token_value == "false") return JsonToken::BOOLEAN_TRUE;
|
|
if (c == 't' && _token_value == "true") return JsonToken::BOOLEAN_FALSE;
|
|
|
|
return JsonToken::STRING;
|
|
}
|
|
|
|
switch (c)
|
|
{
|
|
case ',':
|
|
return JsonToken::COMMA;
|
|
case ':':
|
|
return JsonToken::COLON;
|
|
case ';':
|
|
return JsonToken::SEMICOLON;
|
|
case '=':
|
|
return JsonToken::EQUALS;
|
|
case '{':
|
|
return JsonToken::OBJECT_BEGIN;
|
|
case '}':
|
|
return JsonToken::OBJECT_END;
|
|
case '[':
|
|
return JsonToken::ARRAY_BEGIN;
|
|
case ']':
|
|
return JsonToken::ARRAY_END;
|
|
default:
|
|
assert(0);
|
|
return JsonToken::NONE;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
void SkipComments()
|
|
{
|
|
char c = ReadChar();
|
|
|
|
if (c == '/')
|
|
{
|
|
_comments += "//";
|
|
for (;;)
|
|
{
|
|
char c = ReadChar();
|
|
if (!c) break;
|
|
_comments += c;
|
|
if (c == '\n')
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (c == '*')
|
|
{
|
|
_comments += "/*";
|
|
for (;;)
|
|
{
|
|
char c = ReadChar();
|
|
if (!c) break;
|
|
_comments += c;
|
|
if (c == '*' && Peek() == '/')
|
|
{
|
|
_comments += ReadChar();
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
SetError("Couldnt parse comment");
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
JsonToken _token = JsonToken::NONE;
|
|
const char *_ptr;
|
|
std::string _str;
|
|
|
|
std::string _path;
|
|
int _line = 1;
|
|
int _column = 1;
|
|
|
|
bool _verbose = false;
|
|
bool _error = false;
|
|
std::string _error_message;
|
|
|
|
|
|
std::string _comments;
|
|
std::string _token_value;
|
|
};
|
|
|
|
|
|
enum class JsonValueType
|
|
{
|
|
Invalid,
|
|
Null,
|
|
Object,
|
|
Array,
|
|
Boolean,
|
|
Number,
|
|
String
|
|
};
|
|
|
|
class JsonParser
|
|
{
|
|
JsonTokenizer &tr;
|
|
|
|
public:
|
|
|
|
JsonParser(JsonTokenizer &tokenizer)
|
|
: tr(tokenizer)
|
|
{
|
|
|
|
}
|
|
|
|
|
|
class ArrayReader;
|
|
class ObjectReader;
|
|
class ValueReader;
|
|
|
|
|
|
class ValueReader
|
|
{
|
|
protected:
|
|
bool _end = false;
|
|
bool _inObject = false;
|
|
JsonTokenizer &tr;
|
|
|
|
public:
|
|
|
|
ValueReader( JsonTokenizer &tokenizer)
|
|
:tr(tokenizer)
|
|
{
|
|
}
|
|
|
|
virtual ~ValueReader()
|
|
{
|
|
}
|
|
|
|
bool HasError()
|
|
{
|
|
return tr.HasError();
|
|
}
|
|
|
|
void SetError(const std::string &msg)
|
|
{
|
|
tr.SetError(msg);
|
|
}
|
|
|
|
const std::string &GetErrorMessage() const
|
|
{
|
|
return tr.GetErrorMessage();
|
|
}
|
|
|
|
|
|
bool End()
|
|
{
|
|
return _end || tr.HasError();
|
|
}
|
|
|
|
JsonValueType GetType() const
|
|
{
|
|
switch (tr.Token())
|
|
{
|
|
case JsonToken::BOOLEAN_TRUE:
|
|
case JsonToken::BOOLEAN_FALSE:
|
|
return JsonValueType::Boolean;
|
|
case JsonToken::NUMBER:
|
|
return JsonValueType::Number;
|
|
case JsonToken::STRING:
|
|
return JsonValueType::String;
|
|
case JsonToken::ARRAY_BEGIN:
|
|
return JsonValueType::Array;
|
|
case JsonToken::OBJECT_BEGIN:
|
|
return JsonValueType::Object;
|
|
default:
|
|
return JsonValueType::Invalid;
|
|
}
|
|
}
|
|
|
|
bool IsObject() const
|
|
{
|
|
return GetType() == JsonValueType::Object;
|
|
}
|
|
|
|
bool IsString() const
|
|
{
|
|
return GetType() == JsonValueType::String;
|
|
}
|
|
|
|
bool IsNumber() const
|
|
{
|
|
return GetType() == JsonValueType::Number;
|
|
}
|
|
|
|
bool IsArray() const
|
|
{
|
|
return GetType() == JsonValueType::Array;
|
|
}
|
|
|
|
bool IsBoolean() const
|
|
{
|
|
return GetType() == JsonValueType::Boolean;
|
|
}
|
|
|
|
void ReadString(std::string &s) {
|
|
tr.ReadString(s);
|
|
NextValue();
|
|
}
|
|
|
|
std::string ReadString() {
|
|
std::string s;
|
|
tr.ReadString(s);
|
|
NextValue();
|
|
return s;
|
|
}
|
|
|
|
void ReadNumber(double &v) {
|
|
tr.ReadNumber(v);
|
|
NextValue();
|
|
}
|
|
|
|
void ReadNumber(float &v) {
|
|
tr.ReadNumber(v);
|
|
NextValue();
|
|
}
|
|
|
|
|
|
void ReadNumber(int &v) {
|
|
tr.ReadNumber(v);
|
|
NextValue();
|
|
}
|
|
|
|
int ReadInteger() {
|
|
int v;
|
|
ReadNumber(v);
|
|
return v;
|
|
}
|
|
|
|
bool ReadBoolean(bool b) {
|
|
if (tr.Token() == JsonToken::BOOLEAN_TRUE) {b = true; return true;}
|
|
if (tr.Token() == JsonToken::BOOLEAN_FALSE) {b = false; return true;}
|
|
return false;
|
|
}
|
|
|
|
void ReadArray(std::function<void (ArrayReader &)> reader)
|
|
{
|
|
assert(!_inObject);
|
|
|
|
{
|
|
_inObject = true;
|
|
ArrayReader ar(tr);
|
|
reader(ar);
|
|
_inObject = false;
|
|
}
|
|
NextValue();
|
|
}
|
|
void ReadObject(std::function<void (ObjectReader &)> reader)
|
|
{
|
|
assert(!_inObject);
|
|
|
|
{
|
|
_inObject = true;
|
|
ObjectReader ar(tr);
|
|
reader(ar);
|
|
_inObject = false;
|
|
}
|
|
NextValue();
|
|
}
|
|
|
|
void SkipValue()
|
|
{
|
|
switch(GetType())
|
|
{
|
|
case JsonValueType::Array:
|
|
{
|
|
ReadArray([](ArrayReader &ar) {
|
|
while (!ar.End()) ar.SkipValue();
|
|
});
|
|
break;
|
|
}
|
|
|
|
case JsonValueType::Object:
|
|
{
|
|
ReadObject([](ObjectReader &ar) {
|
|
while (!ar.End()) ar.SkipValue();
|
|
});
|
|
break;
|
|
}
|
|
default:
|
|
tr.NextToken();
|
|
NextValue();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
protected:
|
|
virtual void NextValue()
|
|
{
|
|
}
|
|
|
|
};
|
|
|
|
|
|
class ArrayReader : public ValueReader
|
|
{
|
|
public:
|
|
ArrayReader( JsonTokenizer &tokenizer)
|
|
:ValueReader(tokenizer)
|
|
{
|
|
tr.Consume(JsonToken::ARRAY_BEGIN);
|
|
_end = (tr.Token() == JsonToken::ARRAY_END);
|
|
}
|
|
|
|
|
|
virtual ~ArrayReader()
|
|
{
|
|
// skip all values
|
|
while (!End())
|
|
{
|
|
SkipValue();
|
|
}
|
|
|
|
tr.Consume(JsonToken::ARRAY_END);
|
|
}
|
|
|
|
protected:
|
|
virtual void NextValue() override
|
|
{
|
|
if (tr.Token() == JsonToken::ARRAY_END)
|
|
{
|
|
_end = true;
|
|
return;
|
|
}
|
|
|
|
if (tr.Token() == JsonToken::COMMA)
|
|
{
|
|
tr.NextToken();
|
|
|
|
// allow trailing comma
|
|
if (tr.Token() == JsonToken::ARRAY_END)
|
|
{
|
|
_end = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tr.SetError("Expected comma");
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
class ObjectReader : public ValueReader
|
|
{
|
|
private:
|
|
std::string _key;
|
|
|
|
public:
|
|
ObjectReader(JsonTokenizer &tokenizer)
|
|
:ValueReader(tokenizer)
|
|
{
|
|
tr.Consume(JsonToken::OBJECT_BEGIN);
|
|
|
|
ReadKey();
|
|
}
|
|
|
|
virtual ~ObjectReader()
|
|
{
|
|
// skip all values
|
|
while (!End())
|
|
{
|
|
SkipValue();
|
|
}
|
|
|
|
tr.Consume(JsonToken::OBJECT_END);
|
|
}
|
|
|
|
|
|
const std::string &Key()
|
|
{
|
|
return _key;
|
|
}
|
|
|
|
|
|
void ReadKey()
|
|
{
|
|
if (tr.Token() == JsonToken::OBJECT_END)
|
|
{
|
|
_end = true;
|
|
return;
|
|
}
|
|
|
|
if (tr.Token() != JsonToken::STRING)
|
|
{
|
|
tr.SetError("Expected object key");
|
|
return;
|
|
}
|
|
|
|
tr.ReadString(_key);
|
|
tr.Consume(JsonToken::COLON);
|
|
}
|
|
|
|
protected:
|
|
virtual void NextValue() override
|
|
{
|
|
if (tr.Token() == JsonToken::OBJECT_END)
|
|
{
|
|
_end = true;
|
|
return;
|
|
}
|
|
|
|
if (tr.Token() == JsonToken::COMMA)
|
|
{
|
|
tr.NextToken();
|
|
}
|
|
else
|
|
{
|
|
tr.SetError("Expected comma");
|
|
return;
|
|
}
|
|
|
|
ReadKey();
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
} // namespace
|