diff --git a/backend/src/json.cpp_cant_compile b/backend/src/json.cpp_cant_compile new file mode 100644 index 0000000..8912f47 --- /dev/null +++ b/backend/src/json.cpp_cant_compile @@ -0,0 +1,704 @@ +#include "json.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace parse { + +using namespace mst::json; + +enum class TokTy { + Eof, + Null = std::to_underlying(Type::Null), + False = std::to_underlying(Type::False), + True = std::to_underlying(Type::True), + String, + Float, + Int = '0', + Comma = ',', + Colon = ':', + LBrace = '{', + RBrace = '}', + LBracket = '[', + RBracket = '[', +}; + +struct Tok { + TokTy ty; + std::string_view text; + Loc loc; +}; + +class Tokenizer { +public: + Tokenizer(std::string_view text) + : m_text(text) + , m_len(text.size()) { }; + + auto next() -> Result + { + Loc loc = { m_idx, m_line, m_col }; + size_t* i = &m_idx; + if (*i >= m_len) [[unlikely]] { + return tok(TokTy::Eof, loc); + } + bool matched = false; + while (*i < m_len && std::strchr(" \t\r\n", m_text[*i]) != NULL) { + matched = true; + step(); + } + if (matched) { + return next(); + } + if (strchr(",:[]{}0", m_text[*i]) != NULL) { + auto ty = (TokTy)m_text[*i]; + step(); + return tok(ty, loc); + } + while (*i < m_len && m_text[*i] >= 'a' && m_text[*i] <= 'z') { + matched = true; + step(); + } + if (matched) { + return make_ident_tok(loc, i); + } + if (m_text[*i] >= '1' && m_text[*i] <= '9') { + return make_number_tok(loc, i); + } + if (m_text[*i] == '\"') { + return make_string_tok(loc, i); + } + return std::unexpected(Error { loc, "illegal character" }); + } + +private: + void step() + { + if (m_idx >= m_len) [[unlikely]] { + return; + } + if (m_text[m_idx] == '\n') { + m_line += 1; + m_col = 1; + } else { + m_col += 1; + } + m_idx += 1; + } + + auto tok(TokTy ty, Loc loc) -> Tok + { + return { ty, std::string_view(&m_text[loc.idx], m_idx - loc.idx), loc }; + } + + auto make_ident_tok(Loc loc, size_t* i) -> Result + { + char const* kws[] = { "null", "false", "true" }; + size_t const lens[] = { 4, 5, 4 }; + TokTy tys[] = { TokTy::Null, TokTy::False, TokTy::True }; + for (size_t j = 0; j < sizeof(kws) / sizeof(kws[0]); ++j) { + size_t len = *i - loc.idx; + if (lens[j] == len && strncmp(kws[j], &m_text[loc.idx], len) == 0) { + return tok(tys[j], loc); + } + } + return std::unexpected(Error { loc, "invalid identifier" }); + } + + auto make_number_tok(Loc loc, size_t* i) -> Tok + { + while (*i < m_len && m_text[*i] >= '0' && m_text[*i] <= '9') { + step(); + } + auto ty = TokTy::Int; + if (*i < m_len && m_text[*i] == '.') { + ty = TokTy::Float; + step(); + while (*i < m_len && m_text[*i] >= '0' && m_text[*i] <= '9') { + step(); + } + } + return tok(ty, loc); + } + + auto make_string_tok(Loc loc, size_t* i) -> Result + { + step(); + while (*i < m_len && m_text[*i] != '\"') { + if (m_text[*i] == '\\') { + step(); + if (*i >= m_len) + break; + } + step(); + } + if (*i >= m_len && m_text[*i] != '\"') [[unlikely]] { + return std::unexpected(Error { loc, "malformed string" }); + } + step(); + return tok(TokTy::String, loc); + } + + std::string_view m_text; + size_t m_len; + size_t m_idx = 0; + int m_line = 1; + int m_col = 1; +}; + +#define CHECK(EXPR) \ + do { \ + if (!(EXPR).has_value()) [[unlikely]] { \ + return std::unexpected((EXPR).error()); \ + } \ + } while (false) + +auto literal_to_string(std::string_view text) + -> Result +{ + auto result = std::string(); + for (size_t i = 1; i < text.size() - 2; ++i) { + if (text[i] == '\\') [[unlikely]] { + i += 1; + if (i >= text.size()) [[unlikely]] { + return std::unexpected("malformed string"); + } + switch (text[i]) { + case 'b': + result += '\b'; + break; + case 'f': + result += '\f'; + break; + case 'n': + result += '\n'; + break; + case 'r': + result += '\r'; + break; + case 't': + result += '\t'; + break; + case 'u': + return std::unexpected("uXXXX in string not supported"); + default: + result += text[i]; + } + } else { + result += text[i]; + } + } + return result; +} + +class Parser { +public: + Parser(std::string_view text) + : m_tokenizer(text) + { + step().value(); + } + + auto parse() -> Result> + { + Loc loc = m_tok.loc; + TokTy* ty = &m_tok.ty; + + if (*ty == TokTy::Null || *ty == TokTy::False || *ty == TokTy::True) { + auto val = std::make_unique(static_cast(*ty)); + CHECK(step()); + return val; + } else if (*ty == TokTy::Int) { + int64_t value = std::strtol(m_tok.text.data(), NULL, 10); + auto val = std::make_unique(Type::I64, value); + CHECK(step()); + return val; + } else if (*ty == TokTy::Float) { + double value = std::strtod(m_tok.text.data(), NULL); + auto val = std::make_unique(Type::F64, value); + CHECK(step()); + return val; + } else if (*ty == TokTy::String) { + auto string_value = literal_to_string(m_tok.text); + if (!string_value) { + return std::unexpected(Error { loc, string_value.error() }); + } + auto val = std::make_unique(Type::String, *string_value); + CHECK(step()); + return val; + } else if (std::to_underlying(*ty) == '[') { + return parse_array(ty); + } else if (std::to_underlying(*ty) == '{') { + return parse_object(ty); + } else { + return std::unexpected(Error { loc, "expected expression" }); + } + } + +private: + auto parse_array(TokTy* ty) -> Result> + { + CHECK(step()); + auto values = Value::Array(); + bool tail = false; + while (*ty != TokTy::Eof + && ((!tail && std::to_underlying(*ty) != ']') + || (tail && std::to_underlying(*ty) == ','))) { + if (tail) + CHECK(step()); + auto child = parse(); + CHECK(child); + values.push_back(std::move(*child)); + tail = true; + } + if (*ty == TokTy::Eof || std::to_underlying(*ty) != ']') { + return std::unexpected(Error { m_tok.loc, "expected ']'" }); + } + CHECK(step()); + return std::make_unique(Type::Array, std::move(values)); + } + + auto parse_object(TokTy* ty) -> Result> + { + CHECK(step()); + auto fields = Value::Object(); + bool tail = false; + while (*ty != TokTy::Eof + && ((!tail && std::to_underlying(*ty) != '}') + || (tail && std::to_underlying(*ty) == ','))) { + if (tail) + CHECK(step()); + if (*ty != TokTy::String) { + return std::unexpected(Error { m_tok.loc, "expected string" }); + } + auto key_value = literal_to_string(m_tok.text); + if (!key_value) { + return std::unexpected(Error { m_tok.loc, key_value.error() }); + } + CHECK(step()); + if (std::to_underlying(*ty) != ':') { + return std::unexpected(Error { m_tok.loc, "expected ':'" }); + } + CHECK(step()); + auto child = parse(); + CHECK(child); + fields[*key_value] = std::move(*child); + tail = true; + } + if (*ty == TokTy::Eof || std::to_underlying(*ty) != '}') { + return std::unexpected(Error { m_tok.loc, "expected '}'" }); + } + CHECK(step()); + return std::make_unique(Type::Object, std::move(fields)); + ; + } + + auto step() -> Result + { + auto result = m_tokenizer.next(); + CHECK(result); + m_tok = *result; + return { }; + } + + Tokenizer m_tokenizer; + Tok m_tok = { }; +}; + +} + +namespace query { +using namespace mst::json; + +enum class TokTy { + Eof, + String, + Ident, + Int = '0', + Dot = '.', + LBracket = '[', + RBracket = ']', +}; + +struct Tok { + TokTy ty; + std::string_view text; + size_t idx; +}; + +class Tokenizer { +public: + Tokenizer(std::string_view text) + : m_text(text) { }; + + auto next() -> Result + { + size_t idx = m_idx; + size_t* i = &m_idx; + if (*i >= m_text.size()) { + return tok(TokTy::Eof, idx); + } + bool matched = false; + while (*i < m_text.size() && strchr(" \t\r\n", m_text[*i]) != NULL) { + matched = true; + step(); + } + if (matched) { + return next(); + } + if (strchr(".[]0", m_text[*i]) != NULL) { + auto ty = static_cast(m_text[*i]); + step(); + return tok(ty, idx); + } + while (*i < m_text.size() && m_text[*i] >= 'a' && m_text[*i] <= 'z') { + matched = true; + step(); + } + if (matched) { + return tok(TokTy::Ident, idx); + } + if (m_text[*i] >= '1' && m_text[*i] <= '9') { + return make_number_tok(idx, i); + } + if (m_text[*i] == '\"') { + return make_string_tok(idx, i); + } + return std::unexpected("illegal character"); + } + +private: + auto make_number_tok(size_t idx, size_t* i) -> Tok + { + while (*i < m_text.size() && m_text[*i] >= '0' && m_text[*i] <= '9') { + step(); + } + return tok(TokTy::Int, idx); + } + + auto make_string_tok(size_t idx, size_t* i) -> Result + { + step(); + while (*i < m_text.size() && m_text[*i] != '\"') { + if (m_text[*i] == '\\') { + step(); + if (*i >= m_text.size()) + break; + } + step(); + } + if (*i >= m_text.size() && m_text[*i] != '\"') { + return std::unexpected("malformed string"); + } + step(); + return tok(TokTy::String, idx); + } + + auto tok(TokTy ty, size_t idx) -> Tok + { + return { ty, std::string_view(&m_text[idx], this->m_idx - idx), idx }; + } + + void step() + { + if (m_idx >= m_text.size()) + return; + m_idx += 1; + } + + std::string_view m_text; + size_t m_idx = 0; +}; + +enum class PathSegTy { + Eof, + IdentKey, + StringKey, + Idx, +}; + +struct PathSeg { + PathSegTy ty; + Tok tok; +}; + +class Parser { +public: + Parser(std::string_view text) + : m_tokenizer(text) + { + } + + auto next() -> Result + { + auto ty = &m_tok.ty; + + if (*ty == TokTy::Eof) { + return PathSeg { .ty = PathSegTy::Eof, .tok = m_tok }; + } else if (std::to_underlying(*ty) == '.') { + CHECK(step()); + auto tok = m_tok; + if (*ty == TokTy::Eof || *ty != TokTy::Ident) { + return std::unexpected("expected identifier"); + } + CHECK(step()); + return PathSeg { .ty = PathSegTy::IdentKey, .tok = tok }; + } else if (std::to_underlying(*ty) == '[') { + CHECK(step()); + auto tok = m_tok; + PathSegTy seg_ty; + if (tok.ty == TokTy::Int) { + seg_ty = PathSegTy::Idx; + } else if (tok.ty == TokTy::String) { + seg_ty = PathSegTy::StringKey; + } else { + return std::unexpected("expected string or integer"); + } + CHECK(step()); + if (*ty == TokTy::Eof || std::to_underlying(*ty) != ']') { + return std::unexpected("expected ']'"); + } + CHECK(step()); + return PathSeg { .ty = seg_ty, .tok = tok }; + } else { + return std::unexpected("expected expression"); + } + } + +private: + auto step() -> Result + { + auto result = m_tokenizer.next(); + CHECK(result); + m_tok = *result; + return { }; + } + + Tokenizer m_tokenizer; + Tok m_tok = { }; +}; + +template + requires std::same_as, Value> +auto resolve(Parser& parser, ValueT& node) -> Result +{ + auto seg = parser.next(); + CHECK(seg); + switch (seg->ty) { + case PathSegTy::Eof: + return &node; + case PathSegTy::IdentKey: { + if (!node.is(Type::Object)) + return std::unexpected("expected object"); + auto key = std::string(seg->tok.text); + if (!node.has(key)) + return std::unexpected( + std::format("no field with key '{}' in object", key)); + auto& child = node[key]; + return resolve(parser, child); + } + case PathSegTy::StringKey: { + if (!node.is(Type::Object)) + return std::unexpected("expected object"); + auto key = parse::literal_to_string(seg->tok.text); + CHECK(key); + if (!node.has(*key)) + return std::unexpected( + std::format("no field with key '{}' in object", *key)); + auto& child = node[*key]; + return resolve(parser, child); + } + case PathSegTy::Idx: { + if (!node.is(Type::Array)) + return std::unexpected("expected array"); + auto idx = strtoull(seg->tok.text.data(), NULL, 10); + if (!node.has(idx)) + return std::unexpected(std::format( + "array index {} out of bounds {}", idx, node.size())); + auto& child = node[idx]; + return resolve(parser, child); + } + } + assert(false); +} + +} + +namespace stringify { +using namespace mst::json; + +auto string_to_literal(std::string_view text) -> std::string +{ + auto result = std::string(); + result += '"'; + for (auto ch : text) { + switch (ch) { + case '\b': + result += "\\b"; + break; + case 'f': + result += "\\f"; + break; + case 'n': + result += "\\n"; + break; + case 'r': + result += "\\r"; + break; + case 't': + result += "\\t"; + break; + case '\\': + result += "\\\\"; + break; + default: + break; + result += ch; + } + } + result += '"'; + return result; +} + +template void write_indent(Writer& writer, int depth) +{ + for (int i = 0; i < depth; ++i) { + std::format(writer, " "); + } +} + +template +void write(Writer& writer, const Value& node, int depth = 0) +{ + switch (node.type()) { + case Type::Null: + std::format(writer, "null"); + break; + case Type::False: + std::format(writer, "false"); + break; + case Type::True: + std::format(writer, "true"); + break; + case Type::I64: + std::format(writer, "{}", node.get_i64()); + break; + case Type::F64: + std::format(writer, "{}", node.get_f64()); + break; + case Type::String: + std::format(writer, "{}", string_to_literal(node.get_string())); + break; + case Type::Array: { + std::format(writer, "["); + auto first = true; + for (const auto& child : node.get_underlying_array()) { + if (!first) { + std::format(writer, ","); + } + first = false; + + if constexpr (profile == WriteProfile::Pretty) { + std::format(writer, "\n"); + write_indent(writer, depth + 1); + } + write(writer, child, depth + 1); + } + if constexpr (profile == WriteProfile::Pretty) { + std::format(writer, "\n"); + write_indent(writer, depth); + } + std::format(writer, "]"); + break; + } + case Type::Object: { + std::format(writer, "{"); + auto first = true; + for (const auto& [key, child] : node.get_underlying_object()) { + if (!first) { + std::format(writer, ","); + } + first = false; + + if constexpr (profile == WriteProfile::Pretty) { + std::format(writer, "\n"); + write_indent(writer, depth + 1); + } + write(writer, string_to_literal(key)); + write(writer, ":"); + if constexpr (profile == WriteProfile::Pretty) { + std::format(writer, " "); + } + write(writer, child, depth + 1); + } + if constexpr (profile == WriteProfile::Pretty) { + std::format(writer, "\n"); + write_indent(writer, depth); + } + std::format(writer, "}"); + break; + } + } +} + +} + +namespace mst::json { + +auto Value::query(std::string_view path) & -> Result +{ + auto parser = query::Parser(path); + return query::resolve(parser, *this); +} + +auto Value::query( + std::string_view path) const& -> Result +{ + auto parser = query::Parser(path); + return query::resolve(parser, *this); +} + +auto Value::write(std::FILE* file, WriteProfile profile) const +{ + if (profile == WriteProfile::Minified) { + stringify::write(file, *this); + } else { + stringify::write(file, *this); + } +} + +auto Value::write(std::ostream& stream, WriteProfile profile) const +{ + if (profile == WriteProfile::Minified) { + stringify::write(stream, *this); + } else { + stringify::write(stream, *this); + } +} + +auto Value::to_string(WriteProfile profile) -> std::string +{ + auto stream = std::stringstream(); + if (profile == WriteProfile::Minified) { + stringify::write( + stream, *this); + } else { + stringify::write( + stream, *this); + } + return stream.str(); +} + +auto parse(std::string_view text) -> Result> +{ + return parse::Parser(text).parse(); +} + +} diff --git a/backend/src/json.hpp b/backend/src/json.hpp new file mode 100644 index 0000000..116cc0a --- /dev/null +++ b/backend/src/json.hpp @@ -0,0 +1,285 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mst::json { + +struct Loc { + size_t idx; + int line; + int col; +}; + +struct Error { + Loc loc; + std::string message; +}; + +template using Result = std::expected; + +enum class Type { + Null = 1, + False, + True, + I64, + F64, + String, + Array, + Object, +}; + +enum class WriteProfile { + Minified, + Pretty, +}; + +class Value { +public: + using I64 = std::int64_t; + using F64 = double; + using String = std::string; + using Array = std::vector>; + using Object = std::unordered_map>; + + using Data = std::variant; + + Value(const Value&) = delete; + Value(Value&&) = delete; + Value& operator=(const Value&) = delete; + Value& operator=(Value&&) = delete; + + static auto make_null() -> std::unique_ptr + { + return std::make_unique(Type::Null); + } + static auto make_bool(bool value) -> std::unique_ptr + { + return std::make_unique(value ? Type::True : Type::False); + } + static auto make_i64(I64 value) -> std::unique_ptr + { + return std::make_unique(Type::I64, value); + } + static auto make_f64(F64 value) -> std::unique_ptr + { + return std::make_unique(Type::F64, value); + } + static auto make_string(std::string value) -> std::unique_ptr + { + return std::make_unique(Type::String, std::move(value)); + } + static auto make_array() -> std::unique_ptr + { + return std::make_unique(Type::Array, Array()); + } + static auto make_object() -> std::unique_ptr + { + return std::make_unique(Type::Object, Object()); + } + + explicit Value(Type type) + : m_type(std::move(type)) + , m_data(std::monostate()) + { + } + + explicit Value(Type type, Data data) + : m_type(std::move(type)) + , m_data(std::move(data)) + { + } + + inline auto type() const -> Type + { + return m_type; + } + + inline auto is(Type type) const -> bool + { + return m_type == type; + } + + inline auto get_bool() const -> bool + { + assert(is(Type::False) || is(Type::True)); + return is(Type::True); + } + + inline auto get_i64() const -> I64 + { + assert(is(Type::I64)); + return std::get(m_data); + } + + inline auto get_f64() const -> F64 + { + assert(is(Type::F64)); + return std::get(m_data); + } + + inline auto get_string() & -> String& + { + assert(is(Type::String)); + return std::get(m_data); + } + inline auto get_string() const& -> const String& + { + assert(is(Type::String)); + return std::get(m_data); + } + inline auto get_underlying_array() & -> Array& + { + assert(is(Type::Array)); + return std::get(m_data); + } + inline auto get_underlying_array() const& -> const Array& + { + assert(is(Type::Array)); + return std::get(m_data); + } + inline auto get_underlying_object() & -> Object& + { + assert(is(Type::Object)); + return std::get(m_data); + } + inline auto get_underlying_object() const& -> const Object& + { + assert(is(Type::Object)); + return std::get(m_data); + } + + inline auto operator[](size_t idx) & -> Value& + { + return *get_underlying_array().at(idx); + } + inline auto operator[](size_t idx) const& -> const Value& + { + return *get_underlying_array().at(idx); + } + inline auto operator[](const std::string& key) -> Value& + { + return *get_underlying_object().at(std::string(key)); + } + inline auto operator[](const std::string& key) const -> const Value& + { + return *get_underlying_object().at(std::string(key)); + } + + inline auto size() const -> size_t + { + assert(is(Type::Array) || is(Type::Object)); + if (is(Type::Array)) + return get_underlying_array().size(); + return get_underlying_object().size(); + } + inline auto has(size_t idx) const -> bool + { + return idx < get_underlying_array().size(); + } + inline auto has(const std::string& key) const -> bool + { + return get_underlying_object().contains(key); + } + + inline auto clone() const -> std::unique_ptr + { + return std::make_unique(m_type, Data(m_data)); + } + + inline auto push(std::unique_ptr value) + { + get_underlying_array().push_back(std::move(value)); + } + inline auto push(Value&& value) + { + get_underlying_array().push_back( + std::make_unique(value.m_type, std::move(value.m_data))); + } + [[deprecated("move or use explicit .clone() instead")]] + inline auto push(Value& value) + { + get_underlying_array().push_back( + std::make_unique(value.m_type, std::move(value.m_data))); + } + + inline auto set(const std::string& key, std::unique_ptr value) + { + get_underlying_object()[key] = std::move(value); + } + inline auto set(const std::string& key, Value&& value) + { + get_underlying_object()[key] + = std::make_unique(value.m_type, std::move(value.m_data)); + } + [[deprecated("use explicit .clone() instead")]] + inline auto set(const std::string& key, Value& value) + { + get_underlying_object()[key] + = std::make_unique(value.m_type, std::move(value.m_data)); + } + + inline void set_null() + { + m_type = Type::Null; + m_data = std::monostate(); + } + inline void set_bool(bool value) + { + m_type = value ? Type::True : Type::False; + m_data = std::monostate(); + } + inline void set_i64(I64 value) + { + m_type = Type::I64; + m_data = value; + } + inline void set_f64(F64 value) + { + m_type = Type::F64; + m_data = value; + } + inline void set_string(String value) + { + m_type = Type::String; + m_data = std::move(value); + } + inline void set_array() + { + m_type = Type::Array; + m_data = Array(); + } + inline void set_object() + { + m_type = Type::Object; + m_data = Object(); + } + + auto query(std::string_view path) & -> Result; + auto query( + std::string_view path) const& -> Result; + + auto write( + std::FILE* file, WriteProfile profile = WriteProfile::Minified) const; + auto write(std::ostream& stream, + WriteProfile profile = WriteProfile::Minified) const; + auto to_string(WriteProfile profile = WriteProfile::Minified) + -> std::string; + +private: + Type m_type; + Data m_data; +}; + +auto parse(std::string_view text) -> Result>; + +} diff --git a/backend/src/main.cpp b/backend/src/main.cpp index 6ed9de0..1e501a7 100644 --- a/backend/src/main.cpp +++ b/backend/src/main.cpp @@ -16,16 +16,16 @@ int main(void) { - auto client = mst::mqtt::Client("localhost", 1883, "test", "1234"); + auto mqtt_client = mst::mqtt::Client("localhost", 1883, "test", "1234"); - client.subscribe("/skateboard/update", [&](std::string_view text) { + mqtt_client.subscribe("/skateboard/update", [&](std::string_view text) { // std::println("Skateboard: {}", text); }); auto mqtt_thread = std::thread([&]() { try { - client.run(); + mqtt_client.run(); } catch (mst::mqtt::Error& ex) { std::println(stderr, "MQTT Client failed: {}", ex.what()); std::abort(); @@ -33,7 +33,7 @@ int main(void) }); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - client.publish("/", "published from c++"); + mqtt_client.publish("/", "published from c++"); auto mgr = mst::event::Manager::create().value(); auto x = mst::Server::bind(mgr, "0.0.0.0", PORT);