This commit is contained in:
sfja 2026-03-30 00:37:19 +02:00
parent e1f9561937
commit 5238b38ef0
3 changed files with 993 additions and 4 deletions

View File

@ -0,0 +1,704 @@
#include "json.hpp"
#include <concepts>
#include <cstddef>
#include <cstring>
#include <format>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
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<Tok>
{
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<Tok>
{
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<Tok>
{
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<std::string, std::string>
{
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<std::unique_ptr<Value>>
{
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<Value>(static_cast<Type>(*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<Value>(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<Value>(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<Value>(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<std::unique_ptr<Value>>
{
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<Value>(Type::Array, std::move(values));
}
auto parse_object(TokTy* ty) -> Result<std::unique_ptr<Value>>
{
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<Value>(Type::Object, std::move(fields));
;
}
auto step() -> Result<void>
{
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<Tok, std::string>
{
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<TokTy>(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<Tok, std::string>
{
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<PathSeg, std::string>
{
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<void, std::string>
{
auto result = m_tokenizer.next();
CHECK(result);
m_tok = *result;
return { };
}
Tokenizer m_tokenizer;
Tok m_tok = { };
};
template <typename ValueT>
requires std::same_as<std::remove_cvref_t<ValueT>, Value>
auto resolve(Parser& parser, ValueT& node) -> Result<ValueT*, std::string>
{
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 <typename Writer> void write_indent(Writer& writer, int depth)
{
for (int i = 0; i < depth; ++i) {
std::format(writer, " ");
}
}
template <typename Writer, WriteProfile profile>
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<Value*, std::string>
{
auto parser = query::Parser(path);
return query::resolve(parser, *this);
}
auto Value::query(
std::string_view path) const& -> Result<const Value*, std::string>
{
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<std::FILE*, WriteProfile::Minified>(file, *this);
} else {
stringify::write<std::FILE*, WriteProfile::Pretty>(file, *this);
}
}
auto Value::write(std::ostream& stream, WriteProfile profile) const
{
if (profile == WriteProfile::Minified) {
stringify::write<std::ostream, WriteProfile::Minified>(stream, *this);
} else {
stringify::write<std::ostream, WriteProfile::Pretty>(stream, *this);
}
}
auto Value::to_string(WriteProfile profile) -> std::string
{
auto stream = std::stringstream();
if (profile == WriteProfile::Minified) {
stringify::write<std::stringstream, WriteProfile::Minified>(
stream, *this);
} else {
stringify::write<std::stringstream, WriteProfile::Pretty>(
stream, *this);
}
return stream.str();
}
auto parse(std::string_view text) -> Result<std::unique_ptr<Value>>
{
return parse::Parser(text).parse();
}
}

285
backend/src/json.hpp Normal file
View File

@ -0,0 +1,285 @@
#pragma once
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <expected>
#include <memory>
#include <ostream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <variant>
#include <vector>
namespace mst::json {
struct Loc {
size_t idx;
int line;
int col;
};
struct Error {
Loc loc;
std::string message;
};
template <typename V, typename E = Error> using Result = std::expected<V, E>;
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<std::unique_ptr<Value>>;
using Object = std::unordered_map<std::string, std::unique_ptr<Value>>;
using Data = std::variant<std::monostate, I64, F64, String, Array, Object>;
Value(const Value&) = delete;
Value(Value&&) = delete;
Value& operator=(const Value&) = delete;
Value& operator=(Value&&) = delete;
static auto make_null() -> std::unique_ptr<Value>
{
return std::make_unique<Value>(Type::Null);
}
static auto make_bool(bool value) -> std::unique_ptr<Value>
{
return std::make_unique<Value>(value ? Type::True : Type::False);
}
static auto make_i64(I64 value) -> std::unique_ptr<Value>
{
return std::make_unique<Value>(Type::I64, value);
}
static auto make_f64(F64 value) -> std::unique_ptr<Value>
{
return std::make_unique<Value>(Type::F64, value);
}
static auto make_string(std::string value) -> std::unique_ptr<Value>
{
return std::make_unique<Value>(Type::String, std::move(value));
}
static auto make_array() -> std::unique_ptr<Value>
{
return std::make_unique<Value>(Type::Array, Array());
}
static auto make_object() -> std::unique_ptr<Value>
{
return std::make_unique<Value>(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<I64>(m_data);
}
inline auto get_f64() const -> F64
{
assert(is(Type::F64));
return std::get<F64>(m_data);
}
inline auto get_string() & -> String&
{
assert(is(Type::String));
return std::get<String>(m_data);
}
inline auto get_string() const& -> const String&
{
assert(is(Type::String));
return std::get<String>(m_data);
}
inline auto get_underlying_array() & -> Array&
{
assert(is(Type::Array));
return std::get<Array>(m_data);
}
inline auto get_underlying_array() const& -> const Array&
{
assert(is(Type::Array));
return std::get<Array>(m_data);
}
inline auto get_underlying_object() & -> Object&
{
assert(is(Type::Object));
return std::get<Object>(m_data);
}
inline auto get_underlying_object() const& -> const Object&
{
assert(is(Type::Object));
return std::get<Object>(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<Value>
{
return std::make_unique<Value>(m_type, Data(m_data));
}
inline auto push(std::unique_ptr<Value> value)
{
get_underlying_array().push_back(std::move(value));
}
inline auto push(Value&& value)
{
get_underlying_array().push_back(
std::make_unique<Value>(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>(value.m_type, std::move(value.m_data)));
}
inline auto set(const std::string& key, std::unique_ptr<Value> 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>(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>(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<Value*, std::string>;
auto query(
std::string_view path) const& -> Result<const Value*, std::string>;
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<std::unique_ptr<Value>>;
}

View File

@ -16,16 +16,16 @@
int main(void) 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); std::println("Skateboard: {}", text);
}); });
auto mqtt_thread = std::thread([&]() { auto mqtt_thread = std::thread([&]() {
try { try {
client.run(); mqtt_client.run();
} catch (mst::mqtt::Error& ex) { } catch (mst::mqtt::Error& ex) {
std::println(stderr, "MQTT Client failed: {}", ex.what()); std::println(stderr, "MQTT Client failed: {}", ex.what());
std::abort(); std::abort();
@ -33,7 +33,7 @@ int main(void)
}); });
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 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 mgr = mst::event::Manager::create().value();
auto x = mst::Server::bind(mgr, "0.0.0.0", PORT); auto x = mst::Server::bind(mgr, "0.0.0.0", PORT);