Files
essim/src/core/domain/persist.cpp
François 63ca17d048 build: split core/ from frontends/; prepare for multiple GUI/TUI targets
Reorganise the tree into business vs frontend as separate directories:
  src/core/{domain,imports,app}   (was system/, imports/, app/)
  src/frontends/tui/              (was tui/ + main.cpp)
  tests/tui/                      (the FTXUI-coupled helper test)
All cross-dir #include paths rewritten; same-dir includes untouched.

CMake: essim_core is the frontend-agnostic business library — links libzip,
pugixml and bsdl, NO GUI toolkit. Each frontend is a self-contained
src/frontends/<name>/ (own CMakeLists, toolkit, main.cpp) that links
essim_core, selected with -DESSIM_FRONTEND=<name> (default tui; 'none' = core +
tests only, no toolkit fetched). FTXUI moved into the tui frontend. Tests are
split: essim_tests links essim_core (no FTXUI), essim_tui_tests links essim_tui.

Verified: default tui build green (ctest 2/2); ESSIM_FRONTEND=none builds the
core + tests with FTXUI never fetched and no `essim` binary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:33:06 +02:00

193 lines
7.0 KiB
C++

#include "persist.hpp"
#include "bsdl_model.hpp"
#include "connect.hpp"
#include "modules.hpp"
#include "parts.hpp"
#include "pins.hpp"
#include "signals.hpp"
#include "system.hpp"
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
namespace {
// Tab-delimited tokeniser. Empty trailing fields are preserved.
std::vector<std::string> split_tab(const std::string &line) {
std::vector<std::string> out;
std::string cur;
for (char c : line) {
if (c == '\t') { out.push_back(std::move(cur)); cur.clear(); }
else cur.push_back(c);
}
out.push_back(std::move(cur));
return out;
}
} // namespace
bool save_system(const System *sys, const std::string &filename, std::string &error)
{
if (!sys) { error = "no system to save"; return false; }
std::ofstream f(filename);
if (!f) { error = "cannot open " + filename + " for writing"; return false; }
f << "# essim system snapshot v1\n";
for (auto &mkv : *sys->modules()) {
Module *mod = mkv.second;
f << "M\t" << mod->name << "\n";
for (auto &pkv : *mod) {
Part *p = pkv.second;
f << "P\t" << p->name << "\t" << p->connector_type << "\n";
if (!p->bsdl_path.empty())
f << "B\t" << p->bsdl_path << "\n";
for (auto &nkv : *p) {
Pin *pin = nkv.second;
Signal *s = pin->signal();
f << "N\t" << pin->name << "\t" << (s ? s->name : "");
const char *otag = nc_origin_tag(pin->nc_origin);
if (*otag) f << "\t" << otag;
f << "\n";
}
}
// Signal types: only persist non-default (Other) overrides.
for (auto &skv : *mod->signals) {
Signal *s = skv.second;
if (s->type == SignalType::Other) continue;
f << "S\t" << s->name << "\t" << signal_type_name(s->type) << "\n";
}
}
for (auto &ckv : *sys->connections()) {
Connection *c = ckv.second;
f << "C\t" << c->name
<< "\t" << (c->m1 ? c->m1->name : "")
<< "\t" << (c->p1 ? c->p1->name : "")
<< "\t" << (c->m2 ? c->m2->name : "")
<< "\t" << (c->p2 ? c->p2->name : "")
<< "\t" << c->transform_name << "\n";
for (auto &wp : c->pin_map) {
Pin *a = wp.first;
Pin *b = wp.second;
f << "W"
<< "\t" << (a && a->prnt && a->prnt->prnt ? a->prnt->prnt->name : "")
<< "\t" << (a && a->prnt ? a->prnt->name : "")
<< "\t" << (a ? a->name : "")
<< "\t" << (b && b->prnt && b->prnt->prnt ? b->prnt->prnt->name : "")
<< "\t" << (b && b->prnt ? b->prnt->name : "")
<< "\t" << (b ? b->name : "")
<< "\n";
}
}
return f.good();
}
System *restore_system(const std::string &filename, std::string &error)
{
std::ifstream f(filename);
if (!f) { error = "cannot open " + filename; return nullptr; }
System *sys = new System();
Module *cur_mod = nullptr;
Part *cur_part = nullptr;
Connection *cur_conn = nullptr;
std::string line;
int lineno = 0;
auto fail = [&](const std::string &msg) {
error = "line " + std::to_string(lineno) + ": " + msg;
delete sys;
return nullptr;
};
while (std::getline(f, line)) {
++lineno;
if (line.empty() || line[0] == '#') continue;
auto fs = split_tab(line);
if (fs.empty()) continue;
const std::string &tag = fs[0];
try {
if (tag == "M") {
if (fs.size() < 2) return fail("M needs <name>");
cur_mod = sys->modules()->merge(fs[1]);
cur_part = nullptr;
} else if (tag == "P") {
if (!cur_mod) return fail("P outside module");
if (fs.size() < 2) return fail("P needs <name>");
cur_part = new Part(fs[1]);
if (fs.size() >= 3) cur_part->connector_type = fs[2];
cur_mod->add(cur_part);
} else if (tag == "N") {
if (!cur_part || !cur_mod) return fail("N outside part");
if (fs.size() < 3) return fail("N needs <pin> <signal>");
Pin *pin = new Pin(fs[1]);
cur_part->add(pin);
if (!fs[2].empty()) {
Signal *s = cur_mod->signals->merge(fs[2]);
s->add(pin);
pin->connect(s);
} else if (fs.size() >= 4) {
NcOrigin o;
if (nc_origin_from_tag(fs[3], o)) pin->nc_origin = o;
}
} else if (tag == "S") {
if (!cur_mod) return fail("S outside module");
if (fs.size() < 3) return fail("S needs <signal> <type>");
Signal *s;
try { s = cur_mod->signals->get(fs[1]); }
catch (const std::exception &) { continue; }
SignalType t;
if (signal_type_from_name(fs[2], t)) s->type = t;
} else if (tag == "C") {
if (fs.size() < 7) return fail("C needs <name> <m1> <p1> <m2> <p2> <transform>");
Module *m1 = sys->modules()->get(fs[2]);
Module *m2 = sys->modules()->get(fs[4]);
Part *p1 = m1->get(fs[3]);
Part *p2 = m2->get(fs[5]);
cur_conn = new Connection(fs[1], m1, p1, m2, p2);
cur_conn->transform_name = fs[6];
sys->connections()->add(cur_conn);
} else if (tag == "W") {
if (!cur_conn) return fail("W outside connection");
if (fs.size() < 7) return fail("W needs <m1> <p1> <pin1> <m2> <p2> <pin2>");
Module *m1 = sys->modules()->get(fs[1]);
Module *m2 = sys->modules()->get(fs[4]);
Part *p1 = m1->get(fs[2]);
Part *p2 = m2->get(fs[5]);
Pin *pin1 = p1->get(fs[3]);
Pin *pin2 = p2->get(fs[6]);
cur_conn->pin_map.emplace_back(pin1, pin2);
} else if (tag == "B") {
if (!cur_part) return fail("B outside part");
if (fs.size() < 2) return fail("B needs <path>");
cur_part->bsdl_path = fs[1];
} else {
return fail("unknown tag '" + tag + "'");
}
} catch (const std::exception &e) {
return fail(std::string(tag) + ": " + e.what());
}
}
// Re-derive BSDL-sourced pin specs from each attached model: we persisted the
// .bsd path (`B` tag), not the derived data, so re-parse and re-apply here.
// Models that no longer parse are skipped silently.
for (auto &mkv : *sys->modules()) {
for (auto &pkv : *mkv.second) {
Part *p = pkv.second;
if (p->bsdl_path.empty()) continue;
BsdlModel m = BsdlModel::from_file(p->bsdl_path);
if (m.valid()) apply_bsdl(p, m);
}
}
return sys;
}