/
levovix
/
kittycad
Обзор
Документация
Войти
/
levovix
/
kittycad
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/data/serialization.cpp
561 строка
13 KB
levovix
add: pretty-printing (dumping) any ISerializable
31 дек 2025, 04:46
31 дек 2025, 04:46
073eaeb
Код
Авторство
О чём код?
#include "serialization.h" #include <QObject> #if 0 ParseError::ParseError(std::string_view message, std::stacktrace stacktrace) : std::exception() { msg = message; msg.append(" [ParseError]"); #ifdef DEBUG msg.append("\nstacktrace: \n"); for (auto const& entry : stacktrace) { if (entry.source_file().empty()) { continue; } msg.append(" "); msg.append(entry.source_file()); msg.append(":"); msg.append(std::to_string(entry.source_line())); #if 0 msg.append(" "); msg.append(entry.description()); #endif msg.append("\n"); } #endif } #endif ParseError::ParseError(std::string_view message) : std::exception(), msg(message) {} const char* ParseError::what() const noexcept { return msg.c_str(); } void ISerializable::serialize(Serializator& s) const { s.s << "\"version\":" << m_version; // todo: записывать версию только если она != 0 } void ISerializable::deserialize(Serializator& s, std::string_view fieldName) { readField(s, fieldName, "version", m_version); } std::string_view ISerializable::typeName() const { return "ISerializable"; } std::string ISerializable::typeNameHumanReadable() const { return QObject::tr("ISerializable").toStdString(); } void ISerializable::dumpType(DumpContext& c) const { addDeclarationIndent(c); c.stream << "[" << c.styleId << id() << c.styleSyntax << "] " << c.styleType << typeName() << c.styleNormal; } void ISerializable::dumpFields(DumpContext& c) const { // версию не пишем } void ISerializable::dumpTree(DumpContext& c) const { } void eatSpaces(Serializator& s) { while (isspace(s.s.peek())) { s.s.get(); if (!s.s) { throw ParseError("unexpected EOF"/*, std::stacktrace::current()*/); } } } void eat(Serializator& s, char c) { if (char got = s.s.get(); got != c) { s.s.unget(); throw ParseError( std::string("expected '") + c + std::string("' but got '") + got + "'"/*, std::stacktrace::current()*/ ); } } bool eatOptional(Serializator& s, char c) { if (s.s.peek() == c) { s.s.get(); return true; } else { return false; } } void writeQuoted(std::ostream& s, std::string_view x) { s << '"'; for (char c : x) { if (c >= 0 && c < 32) { s << '\\'; if (c < 10) { s << '0'; } s << (int)c; } else if (c == '\\' || c == '"' || c == '\'') { s << "\\" << c; } else { s << c; } } s << '"'; } void write(Serializator& s, std::string_view x) { writeQuoted(s.s, x); } void read(Serializator& s, std::string& x) { eatSpaces(s); eat(s, '"'); char c = s.s.get(); while (c != '"') { if (c == '\\') { char c2 = s.s.get(); if (isdigit(c2)) { char c3 = s.s.get(); if (!isdigit(c3)) { throw ParseError("expected a digit"/*, std::stacktrace::current()*/); } x.push_back((c2 - '0') * 10 + (c3 - '0')); } else { x.push_back(c2); } } else { x.push_back(c); } c = s.s.get(); if (!s.s) { throw ParseError("unexpected EOF"/*, std::stacktrace::current()*/); } } } void skipUnknownObject(Serializator& s) { while (true) { // todo: ориентироваться на допустимые символы в обекте, вместо символов прерывания объекта if (s.s.bad() || s.s.peek() == ',' || s.s.peek() == ':' || s.s.peek() == '}' || s.s.peek() == ']') { break; } else if (s.s.peek() == '{' || s.s.peek() == '[') { int nested = 1; s.s.get(); while (nested > 0 && s.s.good()) { if (s.s.peek() == '"') { std::string str; read(s, str); } else { char c = s.s.get(); if (c == '{' || c == '[') ++nested; else if (c == ']' || c == '}') --nested; } } } else if (s.s.peek() == '"') { std::string str; read(s, str); } else { s.s.get(); } } } void trace(TraceContext& s, ISerializable const* v) { if (v == nullptr) { return; } if (s.instances.insert(const_cast<ISerializable*>(v)).second) { v->trace(s); } } void write(Serializator &s, ISerializable const& v) { s.s << '{'; v.serialize(s); s.s << '}'; } void read(Serializator &s, ISerializable& v) { eatSpaces(s); eat(s, '{'); while (true) { eatSpaces(s); if (eatOptional(s, ',')) { eatSpaces(s); } char c = s.s.peek(); if (c == '}') { break; } std::string fieldName; read(s, fieldName); eatSpaces(s); eat(s, ':'); eatSpaces(s); auto pos = s.s.tellg(); v.deserialize(s, fieldName); // todo: поддержка чтения нескольких полей с одинаковым названием, "перегруженным" в дочернем классе if (pos == s.s.tellg()) { // unknown field #if 1 skipUnknownObject(s); #elif throw ParseError("unknown field", std::stacktrace::current()); #endif } if (!s.s) { s.s.clear(); s.s.seekg(pos); throw ParseError("parser sream gone bad"/*, std::stacktrace::current()*/); } } eatSpaces(s); eat(s, '}'); } void write(Serializator &s, ISerializable const* v) { if (v == nullptr) { s.s << '0'; } else if (v->m_id == 0 || !s.instances.contains(const_cast<ISerializable*>(v))) { // write inline object s.s << "{"; write(s, v->typeName()); s.s << ":"; write(s, *v); s.s << "}"; } else { s.s << v->m_id; } } void read(Serializator &s, ISerializable*& v) { eatSpaces(s); if (s.s.peek() == '{') { // inline object s.s.get(); std::string typ; read(s, typ); if (!s.rttiDb->constructors.contains(typ)) { v = s.rttiDb->constructors[typ](); } else { throw ParseError("unknown type"/*, std::stacktrace::current()*/); // todo: proxy objects } eatSpaces(s); eat(s, ':'); read(s, *v); eatSpaces(s); eat(s, '}'); } else { int32_t id; read(s, id); if (id == 0) { v = nullptr; } else { if (s.instances_map.contains(id)) { v = s.instances_map[id].get(); } else { throw ParseError("unregistred id"/*, std::stacktrace::current()*/); } } } } void write(Serializator& s, Vec2 v) { s.s << '[' << v.x << ',' << v.y << ']'; } void read(Serializator& s, Vec2& v) { eatSpaces(s); eat(s, '['); eatSpaces(s); s.s >> v.x; eatSpaces(s); eat(s, ','); eatSpaces(s); s.s >> v.y; eatSpaces(s); eatOptional(s, ','); eatSpaces(s); eat(s, ']'); } void write(Serializator& s, Vec3 v) { s.s << '[' << v.x << ',' << v.y << ',' << v.z << ']'; } void read(Serializator& s, Vec3& v) { eatSpaces(s); eat(s, '['); eatSpaces(s); s.s >> v.x; eatSpaces(s); eat(s, ','); eatSpaces(s); s.s >> v.y; eatSpaces(s); eat(s, ','); eatSpaces(s); s.s >> v.z; eatSpaces(s); eatOptional(s, ','); eatSpaces(s); eat(s, ']'); } void write(Serializator& s, bool v) { s.s << (v ? "true" : "false"); } void read(Serializator& s, bool& v) { std::string buf; buf.reserve(5); while (buf.size() < 5 && isalpha(s.s.peek())) { buf.push_back(s.s.get()); } v = buf == "true"; } void writeRoot(Serializator& s, ref<ISerializable> const& v) { s.instances.clear(); TraceContext tctx{s.instances}; trace(tctx, v); // types s.s << "{\"types\":{"; bool needComma = false; for (auto obj : s.instances) { if (needComma) { s.s << ','; } s.s << '"' << obj->m_id << '"' << ':'; write(s, obj->typeName()); needComma = true; } // objects needComma = false; s.s << "},\"objects\":{"; for (auto obj : s.instances) { if (needComma) { s.s << ','; } s.s << '"' << obj->m_id << '"' << ':'; write(s, *obj); needComma = true; } s.s << "},\"root\":" << v->m_id; s.s << "}"; s.instances.clear(); } void readRoot(Serializator& s, ref<ISerializable>& v) { s.instances_map.clear(); eatSpaces(s); eat(s, '{'); // types std::string types_str; read(s, types_str); if (types_str != "types") { throw ParseError("expected \"types\":"/*, std::stacktrace::current()*/); } eatSpaces(s); eat(s, ':'); eatSpaces(s); eat(s, '{'); while (true) { eatSpaces(s); if (eatOptional(s, ',')) { eatSpaces(s); } if (eatOptional(s, '}')) { break; } if (eatOptional(s, '"')) { eatSpaces(s); } int32_t id; s.s >> id; if (eatOptional(s, '"')) { eatSpaces(s); } eatSpaces(s); eat(s, ':'); std::string typ; read(s, typ); if (!s.rttiDb->constructors.contains(typ)) { throw ParseError("unknown type"/*, std::stacktrace::current()*/); // todo: proxy objects } (s.instances_map[id] = ref(s.rttiDb->constructors[typ]()))->m_id = id; if (!s.s) { throw ParseError("unexpected EOF"/*, std::stacktrace::current()*/); } } eatSpaces(s); eat(s, ','); // objects std::string objects_str; read(s, objects_str); if (objects_str != "objects") { throw ParseError("expected \"objects\":"/*, std::stacktrace::current()*/); } eatSpaces(s); eat(s, ':'); eatSpaces(s); eat(s, '{'); while (true) { eatSpaces(s); if (eatOptional(s, ',')) { eatSpaces(s); } if (eatOptional(s, '}')) { break; } if (eatOptional(s, '"')) { eatSpaces(s); } int32_t id; s.s >> id; if (eatOptional(s, '"')) { eatSpaces(s); } eatSpaces(s); eat(s, ':'); if (!s.instances_map.contains(id)) { throw ParseError("unregistred id"/*, std::stacktrace::current()*/); } read(s, *s.instances_map[id]); if (!s.s) { throw ParseError("unexpected EOF"/*, std::stacktrace::current()*/); } } eatSpaces(s); eat(s, ','); // root std::string root_str; read(s, root_str); if (root_str != "root") { throw ParseError("expected \"root\":"/*, std::stacktrace::current()*/); } eatSpaces(s); eat(s, ':'); int32_t root; read(s, root); if (s.instances_map.contains(root)) { v = s.instances_map[root]; } else { throw ParseError("unregistred id"/*, std::stacktrace::current()*/); } eatSpaces(s); eat(s, '}'); s.instances_map.clear(); } void assignMissingIds(Serializator& s, ISerializable* v) { s.instances.clear(); TraceContext tctx{s.instances}; trace(tctx, v); int32_t maxId = 0; bool needsDefragmenting = false; for (auto obj : s.instances) { if (obj->m_id < 0) { needsDefragmenting = true; maxId = 0; break; } if (obj->m_id > maxId) { maxId = obj->m_id; } } ++maxId; for (auto obj : s.instances) { if (needsDefragmenting || obj->m_id == 0) { obj->m_id = maxId; ++maxId; } } s.instances.clear(); } void dump(DumpContext& c, ISerializable const& v) { v.dumpType(c); c.stream << c.styleNormal << ":\n"; v.dumpFields(c); v.dumpTree(c); } void dump(DumpContext& c, ISerializable const* v) { if (v == nullptr) { c.stream << c.styleSyntax << "-> " << c.styleId << "nullptr" << c.styleNormal; } else { c.stream << c.styleSyntax << "-> "; DumpContext cInner {0, c.stream}; v->dumpType(cInner); } } void dump(DumpContext& c, ptr<ISerializable> const& v) { if (v.expired()) { c.stream << c.styleSyntax << "-> " << c.styleId << "expired" << c.styleNormal; } else { c.stream << c.styleSyntax << "-> "; DumpContext cInner {0, c.stream}; v.lock()->dumpType(cInner); } } void dumpTree(DumpContext& c, std::string_view name, ISerializable const& v) { ++c.indent; v.dumpType(c); c.stream << " " << c.stylePragma << name << c.styleNormal << ":\n"; v.dumpFields(c); v.dumpTree(c); --c.indent; } void addEmptyLine(DumpContext& c) { c.stream << c.styleSyntax; for (int ind = c.indent; ind > 0; --ind) { c.stream << c.indentStr; } c.stream << c.styleNormal << "\n"; } void addDeclarationIndent(DumpContext& c) { c.stream << c.styleSyntax; for (int ind = c.indent - 1; ind > 0; --ind) { c.stream << c.indentStr; } if (c.indent > 0) { c.stream << c.declareStr; } } void dump(DumpContext& c, Vec2 v) { c.stream << c.styleSyntax << "(" << c.styleNumber << v.x << c.styleSyntax << ", " << c.styleNumber << v.y << c.styleSyntax << ")" << c.styleNormal; } void dump(DumpContext& c, Vec3 v) { c.stream << c.styleSyntax << "(" << c.styleNumber << v.x << c.styleSyntax << ", " << c.styleNumber << v.y << c.styleSyntax << ", " << c.styleNumber << v.z << c.styleSyntax << ")" << c.styleNormal; } void dump(DumpContext& c, bool v) { c.stream << c.styleNumber << (v ? "true" : "false") << c.styleNormal; } void dump(DumpContext& c, std::string const& v) { c.stream << c.styleString; writeQuoted(c.stream, v); c.stream << c.styleNormal; } void throwWithLineInfo(Serializator& s, ParseError&& e, std::string_view filename) { int line = 1, col = 1; int64_t rem = s.s.tellg(); s.s.seekg(0); while (rem > 0) { if (s.s.get() == '\n') { ++line; col = 1; } else { ++col; } --rem; } throw ParseError( std::string(filename) + ":" + std::to_string(line) + ":" + std::to_string(col) + " " + e.msg ); }