Skip to content

Commit

Permalink
json-parser: Implement parser
Browse files Browse the repository at this point in the history
Parse the provided token stream into an in-memory representation wrapped
by the type `json`. So far, the interface to it is very limited and
verbose.

Some tests (but that many) are already provided in the samples directory
as usual.

Signed-off-by: Kristiyan Stoimenov <kristoimenov@gmail.com>
  • Loading branch information
boki1 committed Jun 21, 2023
1 parent c5f310f commit 6a96671
Show file tree
Hide file tree
Showing 15 changed files with 756 additions and 199 deletions.
3 changes: 2 additions & 1 deletion lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
add_library(json-parser
src/tokenizer.cpp
src/parser.cpp)
src/parser.cpp
src/json.cpp)
target_compile_options(json-parser PUBLIC
-Wall -Wextra -Werror -std=c++20)
target_include_directories(json-parser PUBLIC
Expand Down
271 changes: 267 additions & 4 deletions lib/include/json-parser/json.h
Original file line number Diff line number Diff line change
@@ -1,19 +1,282 @@
#ifndef FMI_JSON_PARSER_JSON_INCLUDED
#define FMI_JSON_PARSER_JSON_INCLUDED

#include <string>
#include <vector>
#include <iostream>
#include <concepts>

#include <mystd/unordered_map.h>
#include <mystd/memory.h>
#include <mystd/type_traits.h>
#include <mystd/utility.h>

#include <json-parser/tokenizer.h>

namespace json_parser {

// Used for the operator[] of a json::object.
template<class T>
concept stringable = std::is_convertible_v<T, std::string_view>;

static_assert(stringable<std::string>);
static_assert(stringable<char *>);
static_assert(stringable<const char *>);

class json_exception : public std::exception {
public:
explicit json_exception(std::string msg)
: m_msg { std::move(msg) }
{
}

[[nodiscard]] const char* what() const noexcept { return m_msg.c_str(); }

private:
std::string m_msg;
};

class json {

public:
class value {};
///
/// Types
///

class value;
using pmrvalue = mystd::unique_ptr<value>;

class value {
public:
virtual ~value() noexcept {}
virtual void serialize(std::ostream &os, std::size_t depth) const = 0;
virtual json::pmrvalue clone() const = 0;
};

class keyword : public value {
public:
void serialize(std::ostream &os, std::size_t depth) const override;

explicit keyword(token_keyword data)
: m_data{std::move(data)} {}

json::pmrvalue clone() const override { return mystd::make_unique<keyword>(*this); }

private:
token_keyword m_data;
};

class number : public value {
public:
void serialize(std::ostream &os, std::size_t depth) const override;

explicit number(token_number data)
: m_data{std::move(data)} {}

json::pmrvalue clone() const override { return mystd::make_unique<number>(*this); }

private:
token_number m_data;
};

class string : public value {
public:
// json::string has to be hashable, because it is the
// key type of json::object's inner map container.
struct hasher {
size_t operator()(const json::string &s) const {
return std::hash<std::string>{}(s.m_data.value());
}
};

bool operator==(const json::string &rhs) const { return m_data.value() == rhs.m_data.value(); }
bool operator!=(const json::string &rhs) const { return !(*this == rhs); }

json::pmrvalue clone() const override { return mystd::make_unique<string>(*this); }

public:
void serialize(std::ostream &os, std::size_t depth) const override;

// The json::string is implicitly convertible to std::string, because
// we want to support indexing JSON objects with string objects and literals.
string(std::string data) : m_data{std::move(data)} {}
operator std::string() const { return m_data.value(); }

explicit string(token_string data)
: m_data{std::move(data)} {}

private:
token_string m_data;
};

template <typename DataType, typename IndexType>
class container_value : public value {
friend json;

using data_type = DataType;
using index_type = IndexType;

public:
void serialize(std::ostream &, std::size_t) const override { mystd::unreachable(); }
json::pmrvalue clone() const override { mystd::unreachable(); }

template <typename ...ItemType>
void append(ItemType&& ...) { mystd::unreachable(); }

virtual ~container_value() noexcept = default;

public:
[[nodiscard]] const value &at(index_type i) const { return *(m_data.at(i).get()); }
[[nodiscard]] value &at(index_type i) { return *(m_data.at(i).get()); }

[[nodiscard]] typename data_type::const_iterator cbegin() const { return m_data.cbegin(); }
[[nodiscard]] typename data_type::const_iterator cend() const { return m_data.cend(); }

[[nodiscard]] typename data_type::iterator begin() { return m_data.begin(); }
[[nodiscard]] typename data_type::iterator end() { return m_data.end(); }

[[nodiscard]] std::size_t size() const noexcept { return m_data.size(); }
[[nodiscard]] std::size_t empty() const noexcept { return m_data.empty(); }

protected:
data_type m_data;
};

class object : public container_value<
mystd::unordered_map<json::string, pmrvalue, json::string::hasher>,
std::string>
{
public:
void serialize(std::ostream &os, std::size_t depth) const override {
os << std::string(depth, ' ') << "{";
size_t count = 0;
for (const auto &[str, val] : m_data){
str.serialize(os, depth + 2);
os << ": ";
val->serialize(os, depth + 2);
if (++count < m_data.size() - 1)
os << ",\n";
}
os << std::string(depth, ' ') << "}";
}

template <typename ...ItemType>
void append(ItemType&& ...item_args) {
m_data.emplace(std::forward<ItemType>(item_args)...);
}

json::pmrvalue clone() const override {
auto cloned = mystd::make_unique<object>();
for (const auto &[key, val] : m_data){
auto cloned_key = key.clone();
json::string *cloned_key_ptr = dynamic_cast<json::string *>(cloned_key.get());
assert(cloned_key_ptr != nullptr);
cloned->append(std::move(*cloned_key_ptr), val->clone());
}
return cloned;
}
};

class array : public container_value<
std::vector<pmrvalue>,
std::size_t>
{
public:

void serialize(std::ostream &os, std::size_t depth) const override {
os << std::string(depth, ' ') << "[";
size_t count = 0;
for (const auto &val : m_data) {
val->serialize(os, depth + 2);
if (++count < m_data.size() - 1)
os << ",\n";
}
os << std::string(depth, ' ') << "]";
}

template <typename ...ItemType>
void append(ItemType&& ...item_args) {
m_data.emplace_back(std::forward<ItemType>(item_args)...);
}

json::pmrvalue clone() const override {
auto cloned = std::make_unique<array>();
for (const auto &val : m_data)
cloned->append(val->clone());
return cloned;
}
};

// DEBUG:
static_assert(std::is_default_constructible_v<json::array>);
static_assert(std::is_default_constructible_v<json::object>);

public:
///
/// Accessors
///

[[nodiscard]] const json::value &operator[](auto &&i) const {
if constexpr (std::integral<std::decay_t<decltype(i)>>) {
if (const auto *root_as_array = dynamic_cast<const json::array *>(m_root_node.get()); root_as_array)
return root_as_array->at(i);
}

if constexpr (stringable<decltype(i)>) {
if (const auto *root_as_object = dynamic_cast<const json::object *>(m_root_node.get()); root_as_object)
return root_as_object->at(std::forward<decltype(i)>(i));
}

throw json_exception("Cannot index a non-container JSON type");
}

[[nodiscard]] json::value &operator[](auto &&i) {
if constexpr (std::integral<decltype(i)>) {
if (auto *root_as_array = dynamic_cast<json::array *>(m_root_node.get()); root_as_array)
return root_as_array->at(i);
}

if constexpr (stringable<decltype(i)>) {
if (auto *root_as_object = dynamic_cast<json::object *>(m_root_node.get()); root_as_object)
return root_as_object->at(std::forward<decltype(i)>(i));
}

throw json_exception("Cannot index a non-container JSON type");
}

const json::value * root() const noexcept { return m_root_node.get(); }
json::value *root() noexcept { return m_root_node.get(); }

const json::value &root_unsafe() const { return *(m_root_node.get()); }
json::value &root_unsafe() { return (*m_root_node.get()); }

public:
///
/// Special member functions
///

json() = default;

json(const json &);
json& operator=(const json &);

json(json &&) noexcept = default;
json& operator=(json &&) noexcept = default;

explicit json(json::pmrvalue root_node)
: m_root_node{std::move(root_node)} {}

class object {};
bool operator==(const json &) const noexcept;
bool operator!=(const json &rhs) const noexcept { return !(*this == rhs); }

class array {};
private:
json::pmrvalue m_root_node;
};

class json_builder {};
template <typename NodeType, typename ...T>
json::pmrvalue make_node(T&& ...args) {
return mystd::make_unique<NodeType>(mystd::forward<T>(args)...);
}

} // namespace json_parser

Expand Down
86 changes: 86 additions & 0 deletions lib/include/json-parser/parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#ifndef FMI_JSON_PARSER_PARSER_INCLUDED
#define FMI_JSON_PARSER_PARSER_INCLUDED

#include <mystd/optional.h>

#include <json-parser/tokenizer.h>
#include <json-parser/json.h>

namespace json_parser {

class parser_exception : public std::exception {

public:
explicit parser_exception(std::string msg,
mystd::optional<location> location = {})
: m_location { location }
, m_msg { std::move(msg) }
{
}

[[nodiscard]] const char* what() const noexcept { return m_msg.c_str(); }

private:
mystd::optional<location> m_location;
std::string m_msg;
};

class parser {

public:
explicit parser(const std::string& filename)
: m_tokenizer{filename}
, m_token_cit{m_tokenizer.begin()}
{
}

const json &parse() &;
const json &operator()() & { return parse(); }

json parse() &&;
json operator()() && { return parse(); }

private:
///
/// Parsing behaviour
///

void parse_and_store();
[[nodiscard]] json::pmrvalue parse_object();
[[nodiscard]] json::pmrvalue parse_array();
[[nodiscard]] json::pmrvalue parse_value();

///
/// Error handling and helpers
///

[[nodiscard]] bool has_more() const noexcept { return m_token_cit.has_more(); }

void expect_has_more() const {
if (!m_token_cit.has_more())
throw parser_exception_here("Expected more tokens during parsing.");
}

template <typename TokenKind>
mystd::unique_ptr<token> expect_token() {
expect_has_more();
mystd::unique_ptr<token> next_token = *m_token_cit++;
if (!token_as<TokenKind>(next_token))
throw parser_exception_here(std::string("Expected token of type `") + typeid(TokenKind).name() + "` but no such was found.");
return next_token;
}

template <typename T>
[[nodiscard]] parser_exception parser_exception_here(T&& msg) const {
return parser_exception(mystd::forward<T>(msg), m_token_cit.current_location());
}

private:
tokenizer m_tokenizer;
token_citerator m_token_cit;
mystd::optional<json> m_parsed;
};

}

#endif // FMI_JSON_PARSER_PARSER_INCLUDED
Loading

0 comments on commit 6a96671

Please sign in to comment.