Files
2024-09-19 01:20:23 +08:00

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;
}