AK: Let's put the JSON parsing in a separate class.

This commit is contained in:
Andreas Kling 2019-06-24 13:38:59 +02:00
parent 8392c549a3
commit 266fed8c00
Notes: sideshowbarker 2024-07-19 13:29:32 +09:00
4 changed files with 235 additions and 162 deletions

185
AK/JsonParser.cpp Normal file
View file

@ -0,0 +1,185 @@
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonParser.h>
#include <AK/StringBuilder.h>
namespace AK {
static inline bool is_whitespace(char ch)
{
return ch == ' ' || ch == '\n' || ch == '\t' || ch == '\v' || ch == '\r';
}
char JsonParser::peek() const
{
if (m_index < m_input.length())
return m_input[m_index];
return '\0';
}
char JsonParser::consume()
{
if (m_index < m_input.length())
return m_input[m_index++];
return '\0';
}
template<typename C>
void JsonParser::consume_while(C condition)
{
while (condition(peek()))
consume();
}
template<typename C>
String JsonParser::extract_while(C condition)
{
StringBuilder builder;
while (condition(peek()))
builder.append(consume());
return builder.to_string();
};
void JsonParser::consume_whitespace()
{
consume_while([](char ch) { return is_whitespace(ch); });
}
void JsonParser::consume_specific(char expected_ch)
{
char consumed_ch = consume();
ASSERT(consumed_ch == expected_ch);
}
String JsonParser::consume_quoted_string()
{
consume_specific('"');
auto string = extract_while([](char ch) { return ch != '"'; });
consume_specific('"');
return string;
}
JsonValue JsonParser::parse_object()
{
JsonObject object;
consume_specific('{');
for (;;) {
consume_whitespace();
if (peek() == '}')
break;
consume_whitespace();
auto name = consume_quoted_string();
consume_whitespace();
consume_specific(':');
consume_whitespace();
auto value = parse();
object.set(name, value);
consume_whitespace();
if (peek() == '}')
break;
consume_specific(',');
}
consume_specific('}');
return object;
}
JsonValue JsonParser::parse_array()
{
JsonArray array;
consume_specific('[');
for (;;) {
consume_whitespace();
if (peek() == ']')
break;
array.append(parse());
consume_whitespace();
if (peek() == ']')
break;
consume_specific(',');
}
consume_whitespace();
consume_specific(']');
return array;
}
JsonValue JsonParser::parse_string()
{
return consume_quoted_string();
}
JsonValue JsonParser::parse_number()
{
auto number_string = extract_while([](char ch) { return ch == '-' || (ch >= '0' && ch <= '9'); });
bool ok;
auto value = JsonValue(number_string.to_int(ok));
ASSERT(ok);
return value;
}
void JsonParser::consume_string(const char* str)
{
for (size_t i = 0, length = strlen(str); i < length; ++i)
consume_specific(str[i]);
}
JsonValue JsonParser::parse_true()
{
consume_string("true");
return JsonValue(true);
}
JsonValue JsonParser::parse_false()
{
consume_string("false");
return JsonValue(false);
}
JsonValue JsonParser::parse_null()
{
consume_string("null");
return JsonValue(JsonValue::Type::Null);
}
JsonValue JsonParser::parse_undefined()
{
consume_string("undefined");
return JsonValue(JsonValue::Type::Undefined);
}
JsonValue JsonParser::parse()
{
consume_whitespace();
auto type_hint = peek();
switch (type_hint) {
case '{':
return parse_object();
case '[':
return parse_array();
case '"':
return parse_string();
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return parse_number();
case 'f':
return parse_false();
case 't':
return parse_true();
case 'n':
return parse_null();
case 'u':
return parse_undefined();
}
return JsonValue();
}
}

47
AK/JsonParser.h Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include <AK/JsonValue.h>
namespace AK {
class JsonParser {
public:
explicit JsonParser(const StringView& input)
: m_input(input)
{
}
~JsonParser()
{
}
JsonValue parse();
private:
char peek() const;
char consume();
void consume_whitespace();
void consume_specific(char expected_ch);
void consume_string(const char*);
String consume_quoted_string();
JsonValue parse_array();
JsonValue parse_object();
JsonValue parse_number();
JsonValue parse_string();
JsonValue parse_false();
JsonValue parse_true();
JsonValue parse_null();
JsonValue parse_undefined();
template<typename C>
void consume_while(C);
template<typename C>
String extract_while(C);
StringView m_input;
int m_index { 0 };
};
}
using AK::JsonParser;

View file

@ -1,6 +1,7 @@
#include <AK/Function.h>
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonParser.h>
#include <AK/JsonValue.h>
#include <AK/StringBuilder.h>
@ -176,170 +177,9 @@ String JsonValue::serialized() const
return builder.to_string();
}
static bool is_whitespace(char ch)
{
return ch == ' ' || ch == '\n' || ch == '\t' || ch == '\v' || ch == '\r';
}
JsonValue JsonValue::from_string(const StringView& input)
{
int index = 0;
auto peek = [&] {
return input[index];
};
auto consume = [&]() -> char {
if (index < input.length())
return input[index++];
return '\0';
};
auto consume_while = [&](auto condition) {
while (condition(peek()))
consume();
};
auto extract_while = [&](auto condition) {
StringBuilder builder;
while (condition(peek()))
builder.append(consume());
return builder.to_string();
};
auto consume_whitespace = [&] {
consume_while([](char ch) { return is_whitespace(ch); });
};
auto consume_specific = [&](char expected_ch) {
char consumed_ch = consume();
ASSERT(consumed_ch == expected_ch);
};
Function<JsonValue()> parse;
auto parse_object_member = [&](JsonObject& object) {
consume_whitespace();
consume_specific('"');
auto name = extract_while([](char ch) { return ch != '"'; });
consume_specific('"');
consume_whitespace();
consume_specific(':');
consume_whitespace();
auto value = parse();
object.set(name, value);
};
auto parse_object = [&]() -> JsonValue {
JsonObject object;
consume_specific('{');
for (;;) {
consume_whitespace();
if (peek() == '}')
break;
parse_object_member(object);
consume_whitespace();
if (peek() == '}')
break;
consume_specific(',');
}
consume_specific('}');
return object;
};
auto parse_array = [&]() -> JsonValue {
JsonArray array;
consume_specific('[');
for (;;) {
consume_whitespace();
if (peek() == ']')
break;
array.append(parse());
consume_whitespace();
if (peek() == ']')
break;
consume_specific(',');
}
consume_whitespace();
consume_specific(']');
return array;
};
auto parse_string = [&]() -> JsonValue {
consume_specific('"');
auto string = extract_while([](char ch) { return ch != '"'; });
consume_specific('"');
return JsonValue(string);
};
auto parse_number = [&]() -> JsonValue {
auto number_string = extract_while([](char ch) { return ch == '-' || (ch >= '0' && ch <= '9'); });
bool ok;
auto value = JsonValue(number_string.to_int(ok));
ASSERT(ok);
return value;
};
auto consume_string = [&](const char* str) {
for (size_t i = 0, length = strlen(str); i < length; ++i)
consume_specific(str[i]);
};
auto parse_true = [&]() -> JsonValue {
consume_string("true");
return JsonValue(true);
};
auto parse_false = [&]() -> JsonValue {
consume_string("false");
return JsonValue(false);
};
auto parse_null = [&]() -> JsonValue {
consume_string("null");
return JsonValue(JsonValue::Type::Null);
};
auto parse_undefined = [&]() -> JsonValue {
consume_string("undefined");
return JsonValue(JsonValue::Type::Undefined);
};
parse = [&]() -> JsonValue {
consume_whitespace();
auto type_hint = peek();
switch (type_hint) {
case '{':
return parse_object();
case '[':
return parse_array();
case '"':
return parse_string();
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return parse_number();
case 'f':
return parse_false();
case 't':
return parse_true();
case 'n':
return parse_null();
case 'u':
return parse_undefined();
}
ASSERT_NOT_REACHED();
};
return parse();
return JsonParser(input).parse();
}
}

View file

@ -10,6 +10,7 @@ AK_OBJS = \
../AK/JsonValue.o \
../AK/JsonArray.o \
../AK/JsonObject.o \
../AK/JsonParser.o \
../AK/MappedFile.o
LIBC_OBJS = \