300 lines
8.9 KiB
C++
300 lines
8.9 KiB
C++
#include <stdexcept>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
#include "json.h"
|
|
|
|
static void skip_whitespace(char *&cursor);
|
|
|
|
static std::string parse_key(char *&cursor);
|
|
|
|
static JsonParser::JsonValue parse_value(char *&cursor);
|
|
|
|
static JsonParser::JsonValue parse_object(char *&cursor);
|
|
|
|
static JsonParser::JsonValue parse_array(char *&cursor);
|
|
|
|
static JsonParser::JsonValue parse_string(char *&cursor);
|
|
|
|
static JsonParser::JsonValue parse_number(char *&cursor);
|
|
|
|
static JsonParser::JsonValue parse_boolean(char *&cursor);
|
|
|
|
static JsonParser::JsonValue parse_null(char *&cursor);
|
|
|
|
static void walk(char *&cursor) {
|
|
cursor++;
|
|
}
|
|
|
|
void skip_whitespace(char *&cursor) {
|
|
while (*cursor == ' ' || *cursor == '\t' || *cursor == '\n' || *cursor == '\r') {
|
|
walk(cursor);
|
|
}
|
|
}
|
|
|
|
JsonParser::JsonValue parse_value(char *&cursor) {
|
|
switch (*cursor) {
|
|
case '{':
|
|
return parse_object(cursor);
|
|
case '"':
|
|
return parse_string(cursor);
|
|
case '0' ... '9':
|
|
case '-':
|
|
return parse_number(cursor);
|
|
case 't':
|
|
case 'f':
|
|
return parse_boolean(cursor);
|
|
case 'n':
|
|
return parse_null(cursor);
|
|
case '[':
|
|
return parse_array(cursor);
|
|
default:
|
|
throw std::runtime_error("Unexpected character at start of value");
|
|
}
|
|
}
|
|
|
|
std::string parse_key(char *&cursor) {
|
|
// key is a string, now don't care about the '\' escape character
|
|
if (*cursor != '"') {
|
|
throw std::runtime_error("Expected '\"' at start of key");
|
|
}
|
|
walk(cursor);
|
|
std::string key;
|
|
while (*cursor != '"') {
|
|
key += *cursor;
|
|
walk(cursor);
|
|
}
|
|
walk(cursor);
|
|
return key;
|
|
}
|
|
|
|
JsonParser::JsonValue parse_string(char *&cursor) {
|
|
if (*cursor != '"') {
|
|
throw std::runtime_error("Expected '\"' at start of string");
|
|
}
|
|
walk(cursor);
|
|
std::string value;
|
|
while (*cursor != '"') {
|
|
if (*cursor == '\\') {
|
|
walk(cursor);
|
|
switch (*cursor) {
|
|
case '"':
|
|
case '\\':
|
|
case '/':
|
|
break;
|
|
case 'b':
|
|
value += '\b';
|
|
break;
|
|
case 't':
|
|
value += '\t';
|
|
break;
|
|
case 'n':
|
|
value += '\n';
|
|
break;
|
|
case 'f':
|
|
value += '\f';
|
|
break;
|
|
case 'r':
|
|
value += '\r';
|
|
break;
|
|
case 'u':
|
|
throw std::runtime_error("Unicode escape character is not supported");
|
|
default:
|
|
throw std::runtime_error("Unexpected escape character");
|
|
}
|
|
}
|
|
value += *cursor;
|
|
walk(cursor);
|
|
}
|
|
walk(cursor);
|
|
JsonParser::JsonValue string{};
|
|
string.type = JsonParser::JsonType::STRING;
|
|
string.value.string = new std::string(value);
|
|
return string;
|
|
}
|
|
|
|
JsonParser::JsonValue parse_number(char *&cursor) {
|
|
if (*cursor != '-' && (*cursor < '0' || *cursor > '9')) {
|
|
throw std::runtime_error("Expected digit or '-' at start of number");
|
|
}
|
|
// flag to indicate if the number is negative
|
|
bool is_negative = false;
|
|
// the integer part of the number
|
|
int64_t integer = 0;
|
|
// the decimal part of the number
|
|
double decimal = 0;
|
|
// flag to indicate if the number is an integer
|
|
bool now_integer = true;
|
|
// the current decimal part position
|
|
int now_position = 0;
|
|
if (*cursor == '-') {
|
|
is_negative = true;
|
|
walk(cursor);
|
|
}
|
|
bool end = false;
|
|
while (!end) {
|
|
switch (*cursor) {
|
|
case '0' ... '9':
|
|
if (now_integer) {
|
|
integer = integer * 10 + (*cursor - '0');
|
|
} else {
|
|
decimal += (*cursor - '0') * pow(10, -now_position);
|
|
now_position++;
|
|
}
|
|
walk(cursor);
|
|
break;
|
|
case '.':
|
|
if (now_integer) {
|
|
now_integer = false;
|
|
now_position = 1;
|
|
} else {
|
|
throw std::runtime_error("Unexpected '.' in number");
|
|
}
|
|
walk(cursor);
|
|
break;
|
|
case ' ':
|
|
case '\t':
|
|
case '\n':
|
|
case '\r':
|
|
case ',':
|
|
case '}':
|
|
case ']':
|
|
end = true;
|
|
break;
|
|
default:
|
|
throw std::runtime_error("Unexpected character in number");
|
|
}
|
|
}
|
|
JsonParser::JsonValue number{};
|
|
if (now_integer) {
|
|
number.type = JsonParser::JsonType::INTEGER;
|
|
number.value.integer = is_negative ? -integer : integer;
|
|
} else {
|
|
number.type = JsonParser::JsonType::NUMBER;
|
|
number.value.number = is_negative ? -((double) integer + decimal) : ((double) integer + decimal);
|
|
}
|
|
return number;
|
|
}
|
|
|
|
JsonParser::JsonValue parse_boolean(char *&cursor) {
|
|
if (*cursor == 't') {
|
|
if (strncmp(cursor, "true", 4) == 0) {
|
|
cursor += 4;
|
|
return {JsonParser::JsonType::BOOLEAN, {.boolean = true}};
|
|
}
|
|
} else if (*cursor == 'f') {
|
|
if (strncmp(cursor, "false", 5) == 0) {
|
|
cursor += 5;
|
|
return {JsonParser::JsonType::BOOLEAN, {.boolean = false}};
|
|
}
|
|
}
|
|
throw std::runtime_error("Unexpected character in boolean");
|
|
}
|
|
|
|
JsonParser::JsonValue parse_null(char *&cursor) {
|
|
if (strncmp(cursor, "null", 4) == 0) {
|
|
cursor += 4;
|
|
JsonParser::JsonValue null{};
|
|
null.type = JsonParser::JsonType::NULL_VALUE;
|
|
return null;
|
|
}
|
|
throw std::runtime_error("Unexpected character in null");
|
|
}
|
|
|
|
JsonParser::JsonValue parse_object(char *&cursor) {
|
|
if (*cursor != '{') {
|
|
throw std::runtime_error("Expected '{' at start of object");
|
|
}
|
|
JsonParser::JsonValue object{};
|
|
object.type = JsonParser::JsonType::OBJECT;
|
|
object.value.object = new std::unordered_map<std::string, JsonParser::JsonValue>();
|
|
walk(cursor);
|
|
skip_whitespace(cursor);
|
|
|
|
// now parse key-value pairs
|
|
bool can_next_pair = true;
|
|
std::string key;
|
|
while (true) {
|
|
switch (*cursor) {
|
|
case '}':
|
|
// empty object
|
|
walk(cursor);
|
|
return object;
|
|
case '"':
|
|
if (!can_next_pair) {
|
|
throw std::runtime_error("Expected ',' before next key-value pair");
|
|
}
|
|
can_next_pair = false;
|
|
// start a key-value pair
|
|
key = parse_key(cursor);
|
|
skip_whitespace(cursor);
|
|
if (*cursor != ':') {
|
|
throw std::runtime_error("Expected ':' after key");
|
|
}
|
|
// pass the ':'
|
|
walk(cursor);
|
|
skip_whitespace(cursor);
|
|
// parse the value and emplace the key-value pair
|
|
object.value.object->emplace(key, parse_value(cursor));
|
|
break;
|
|
case ',':
|
|
if (can_next_pair) {
|
|
throw std::runtime_error("Expected key-value pair, not ','");
|
|
}
|
|
can_next_pair = true;
|
|
walk(cursor);
|
|
break;
|
|
default:
|
|
throw std::runtime_error("Unexpected character at start of object");
|
|
}
|
|
skip_whitespace(cursor);
|
|
}
|
|
}
|
|
|
|
static JsonParser::JsonValue parse_array(char *&cursor) {
|
|
if (*cursor != '[') {
|
|
throw std::runtime_error("Expected '[' at start of array");
|
|
}
|
|
JsonParser::JsonValue array{};
|
|
array.type = JsonParser::JsonType::ARRAY;
|
|
array.value.array = new std::vector<JsonParser::JsonValue>();
|
|
walk(cursor);
|
|
skip_whitespace(cursor);
|
|
|
|
// now parse elements
|
|
bool can_next_element = true;
|
|
while (true) {
|
|
switch (*cursor) {
|
|
case ']':
|
|
// empty array
|
|
walk(cursor);
|
|
return array;
|
|
case ',':
|
|
if (can_next_element) {
|
|
throw std::runtime_error("Expected element, not ','");
|
|
}
|
|
can_next_element = true;
|
|
walk(cursor);
|
|
break;
|
|
default:
|
|
if (!can_next_element) {
|
|
throw std::runtime_error("Expected ',' before next element");
|
|
}
|
|
can_next_element = false;
|
|
array.value.array->push_back(parse_value(cursor));
|
|
break;
|
|
}
|
|
skip_whitespace(cursor);
|
|
}
|
|
}
|
|
|
|
JsonParser::JsonValue JsonParser::parse(const char *input) {
|
|
char *cursor = const_cast<char *>(input);
|
|
JsonValue value = parse_object(cursor);
|
|
skip_whitespace(cursor);
|
|
if (*cursor != '\0') {
|
|
throw std::runtime_error("Unexpected character after end of JSON object");
|
|
}
|
|
return value;
|
|
}
|