mirror of
https://github.com/Mercantec-GHC/h5-projekt-mst.git
synced 2026-08-27 05:37:39 +02:00
add json
This commit is contained in:
parent
e1f9561937
commit
5238b38ef0
704
backend/src/json.cpp_cant_compile
Normal file
704
backend/src/json.cpp_cant_compile
Normal 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
285
backend/src/json.hpp
Normal 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>>;
|
||||||
|
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user