"Adjacent" read as jargon; "power management" is the standard EE umbrella for enable/power-good/sense/fault/seq signals (cf. PMIC). Renamed across the board: NameVerdict::PowerMgmt, stats/LoadResult field `mgmt`, analyze tag [Power mgmt] + header "Pwr-mgmt" + glossary, load lines now say "power-management (control/measure — kept as Other)" (TUI / script / wx kept in sync). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
332 lines
13 KiB
C++
332 lines
13 KiB
C++
#include "core/app/script.hpp"
|
|
|
|
#include "core/app/connect.hpp"
|
|
#include "core/app/edit.hpp"
|
|
#include "core/app/export.hpp"
|
|
#include "core/app/load.hpp"
|
|
#include "core/app/verify.hpp"
|
|
|
|
#include "core/domain/analysis.hpp"
|
|
#include "core/domain/connect.hpp"
|
|
#include "core/domain/modules.hpp"
|
|
#include "core/domain/parts.hpp"
|
|
#include "core/domain/persist.hpp"
|
|
#include "core/domain/signal_type.hpp"
|
|
#include "core/domain/signals.hpp"
|
|
#include "core/domain/system.hpp"
|
|
|
|
#include <cctype>
|
|
#include <fstream>
|
|
#include <map>
|
|
#include <ostream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace app {
|
|
|
|
namespace {
|
|
|
|
// Whitespace split with "double quotes" grouping — same rules as the TUI shell.
|
|
std::vector<std::string> tokenize(const std::string &s) {
|
|
std::vector<std::string> out;
|
|
std::string cur;
|
|
bool in_q = false;
|
|
bool has = false;
|
|
for (char c : s) {
|
|
if (c == '"') { in_q = !in_q; has = true; continue; }
|
|
if (!in_q && std::isspace((unsigned char)c)) {
|
|
if (has) { out.push_back(std::move(cur)); cur.clear(); has = false; }
|
|
} else {
|
|
cur.push_back(c);
|
|
has = true;
|
|
}
|
|
}
|
|
if (has) out.push_back(std::move(cur));
|
|
return out;
|
|
}
|
|
|
|
// One script execution: holds the variable table, the System reference (so new/
|
|
// restore can replace it) and the output stream.
|
|
class Runner {
|
|
public:
|
|
Runner(std::unique_ptr<System> &sys, std::ostream &out) : sys_(sys), out_(out) {}
|
|
|
|
// Run a file; `opened` reports whether it could be opened. Returns the count
|
|
// of effective lines; accumulates command errors into `r.errors`.
|
|
int run_file(const std::string &path, int depth, ScriptResult &r, bool &opened) {
|
|
opened = false;
|
|
if (depth > 32) {
|
|
emit("source: nesting too deep, skipping " + path);
|
|
return 0;
|
|
}
|
|
std::ifstream f(path);
|
|
if (!f) return 0;
|
|
opened = true;
|
|
|
|
int count = 0;
|
|
std::string line;
|
|
while (std::getline(f, line)) {
|
|
std::size_t s = line.find_first_not_of(" \t");
|
|
if (s == std::string::npos) continue;
|
|
if (line[s] == '#') continue;
|
|
std::string t = line.substr(s);
|
|
while (!t.empty() && std::isspace((unsigned char)t.back())) t.pop_back();
|
|
if (t.empty()) continue;
|
|
++count;
|
|
if (!exec(t, depth, r)) ++r.errors;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
private:
|
|
void emit(const std::string &line) { out_ << line << '\n'; }
|
|
|
|
// $name / ${name} → variable value; unknown names kept literally.
|
|
std::string expand(const std::string &s) const {
|
|
std::string out;
|
|
std::size_t i = 0;
|
|
while (i < s.size()) {
|
|
if (s[i] != '$') { out.push_back(s[i++]); continue; }
|
|
std::size_t j = i + 1;
|
|
bool braces = (j < s.size() && s[j] == '{');
|
|
if (braces) ++j;
|
|
std::size_t start = j;
|
|
while (j < s.size() && (std::isalnum((unsigned char)s[j]) || s[j] == '_')) ++j;
|
|
std::string name = s.substr(start, j - start);
|
|
if (braces) {
|
|
if (j >= s.size() || s[j] != '}') { out.push_back('$'); ++i; continue; }
|
|
++j;
|
|
}
|
|
if (name.empty()) { out.push_back('$'); ++i; continue; }
|
|
auto it = vars_.find(name);
|
|
if (it != vars_.end()) out += it->second;
|
|
else out += s.substr(i, j - i);
|
|
i = j;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
Module *resolve_module(const std::string &name) {
|
|
try { return sys_->modules()->get(name); }
|
|
catch (const std::exception &) { emit("unknown module: " + name); return nullptr; }
|
|
}
|
|
Part *resolve_part(Module *m, const std::string &name) {
|
|
try { return m->get(name); }
|
|
catch (const std::exception &) {
|
|
emit("part in " + m->name + " not found: " + name);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void render_verify() {
|
|
VerifyReport r = verify(sys_.get());
|
|
for (const auto &m : r.role_mismatches)
|
|
emit(" " + m.module + "/" + m.part + "/" + m.pin + ": expected "
|
|
+ signal_type_name(m.expected) + ", got " + signal_type_name(m.actual)
|
|
+ " (signal: " + m.signal + ")");
|
|
emit("verify: " + std::to_string(r.role_mismatches.size())
|
|
+ " local mismatch(es) over " + std::to_string(r.typed_pins)
|
|
+ " typed pin(s).");
|
|
for (const auto &ni : r.net_inconsistencies) {
|
|
std::string line = " net mixes Power and GndShield:";
|
|
for (const auto &mem : ni.members)
|
|
line += " " + mem.module + "/" + mem.signal
|
|
+ "(" + signal_type_name(mem.type) + ")";
|
|
emit(line);
|
|
}
|
|
emit("verify: " + std::to_string(r.net_inconsistencies.size())
|
|
+ " inconsistent net(s) over " + std::to_string(r.bridged_nets)
|
|
+ " bridged net(s) (" + std::to_string(r.total_nets) + " total).");
|
|
emit("verify: " + std::to_string(r.orphan_total())
|
|
+ " orphan pin(s) at import (" + std::to_string(r.orphan_imported)
|
|
+ " imported NC, " + std::to_string(r.orphan_dropped)
|
|
+ " dropped singleton).");
|
|
auto grp = [&](const std::vector<Anomaly> &v, const char *tail) {
|
|
for (const auto &a : v)
|
|
emit(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message);
|
|
emit("verify: " + std::to_string(v.size()) + tail);
|
|
};
|
|
grp(r.pin_anomalies, " model-driven pin anomaly(ies).");
|
|
grp(r.jtag_anomalies, " JTAG chain anomaly(ies).");
|
|
grp(r.conflict_anomalies, " source-conflict(s).");
|
|
grp(r.completeness_anomalies, " BSDL completeness issue(s).");
|
|
}
|
|
|
|
// Execute one already-trimmed line. Returns false on a hard error.
|
|
bool exec(const std::string &raw, int depth, ScriptResult &top) {
|
|
std::vector<std::string> tok = tokenize(raw);
|
|
if (tok.empty()) return true;
|
|
const std::string cmd = tok[0];
|
|
std::vector<std::string> a;
|
|
for (std::size_t i = 1; i < tok.size(); ++i) a.push_back(expand(tok[i]));
|
|
|
|
auto need = [&](std::size_t n) {
|
|
if (a.size() == n) return true;
|
|
emit(cmd + ": expected " + std::to_string(n) + " argument(s)");
|
|
return false;
|
|
};
|
|
|
|
if (cmd == "set") {
|
|
if (a.size() != 2) { emit("set: usage: set <name> <value>"); return false; }
|
|
vars_[a[0]] = a[1];
|
|
return true;
|
|
}
|
|
if (cmd == "new") {
|
|
sys_ = std::make_unique<System>();
|
|
emit("system created.");
|
|
return true;
|
|
}
|
|
if (cmd == "load") {
|
|
if (!need(3)) return false;
|
|
ImportType t;
|
|
if (!import_type_from_name(a[2], t)) { emit("unknown import type: " + a[2]); return false; }
|
|
LoadResult r = load_module(sys_.get(), a[0], a[1], t);
|
|
if (!r.ok) { emit("load failed: " + r.error); return false; }
|
|
emit("loaded '" + a[0] + "' from " + a[1]);
|
|
emit(" parts: " + std::to_string(r.parts));
|
|
emit(" signals: " + std::to_string(r.signals)
|
|
+ (r.dropped ? " (dropped " + std::to_string(r.dropped)
|
|
+ " singleton/NC signal(s))" : ""));
|
|
emit(" types: " + std::to_string(r.power) + " power, "
|
|
+ std::to_string(r.gnd) + " gnd, " + std::to_string(r.kept_other)
|
|
+ " suspect Power (name only — kept as Other), "
|
|
+ std::to_string(r.mgmt)
|
|
+ " power-management (control/measure — kept as Other)");
|
|
return true;
|
|
}
|
|
if (cmd == "connect" || cmd == "plug") {
|
|
if (!need(4)) return false;
|
|
Module *m1 = resolve_module(a[0]); if (!m1) return false;
|
|
Part *p1 = resolve_part(m1, a[1]); if (!p1) return false;
|
|
Module *m2 = resolve_module(a[2]); if (!m2) return false;
|
|
Part *p2 = resolve_part(m2, a[3]); if (!p2) return false;
|
|
ConnectResult r = connect_parts(sys_.get(), m1, p1, m2, p2);
|
|
if (r.refused) { emit("connect refused: " + r.error); return false; }
|
|
if (!r.identity_info.empty()) {
|
|
emit("connect: " + r.identity_info);
|
|
if (r.nc_added > 0)
|
|
emit("connect: added " + std::to_string(r.nc_added)
|
|
+ " NC pin(s) so both sides match");
|
|
}
|
|
if (!r.ok) { emit("connect failed: " + r.error); return false; }
|
|
emit("connected: " + r.connection_name + " via " + r.transform_name
|
|
+ " (" + std::to_string(r.wires) + " wires)");
|
|
return true;
|
|
}
|
|
if (cmd == "set-connector-type") {
|
|
if (!need(3)) return false;
|
|
Module *m = resolve_module(a[0]); if (!m) return false;
|
|
Part *p = resolve_part(m, a[1]); if (!p) return false;
|
|
SetConnectorTypeResult r = set_connector_type(p, a[2]);
|
|
if (!r.ok) { emit("set-connector-type refused: " + r.error); return false; }
|
|
emit(m->name + "/" + p->name + ": connector_type = "
|
|
+ (a[2].empty() ? "(none)" : a[2]));
|
|
if (r.materialised > 0)
|
|
emit("set-connector-type: added " + std::to_string(r.materialised)
|
|
+ " NC pin(s) from the connector layout");
|
|
return true;
|
|
}
|
|
if (cmd == "set-signal-type") {
|
|
if (!need(3)) return false;
|
|
Module *m = resolve_module(a[0]); if (!m) return false;
|
|
Signal *sig;
|
|
try { sig = m->signals->get(a[1]); }
|
|
catch (const std::exception &) {
|
|
emit("unknown signal: " + m->name + "/" + a[1]); return false;
|
|
}
|
|
SetSignalTypeResult r = set_signal_type(sig, a[2]);
|
|
if (!r.ok) { emit(r.error); return false; }
|
|
emit(m->name + "/" + sig->name + ": signal type = " + signal_type_name(r.type));
|
|
return true;
|
|
}
|
|
if (cmd == "attach-bsdl") {
|
|
if (!need(3)) return false;
|
|
Module *m = resolve_module(a[0]); if (!m) return false;
|
|
Part *p = resolve_part(m, a[1]); if (!p) return false;
|
|
AttachBsdlResult r = attach_bsdl(p, a[2]);
|
|
if (!r.ok) { emit("attach-bsdl: " + r.error); return false; }
|
|
emit(m->name + "/" + p->name + ": attached BSDL '" + r.entity + "' — "
|
|
+ std::to_string(r.bound) + "/" + std::to_string(r.ports_total)
|
|
+ " ports bound" + (r.unbound ? (", " + std::to_string(r.unbound) + " unbound") : ""));
|
|
return true;
|
|
}
|
|
if (cmd == "duplicate") {
|
|
if (!need(2)) return false;
|
|
DuplicateResult r = duplicate_module(sys_.get(), a[0], a[1]);
|
|
if (!r.ok) { emit(r.error); return false; }
|
|
emit("duplicate: '" + a[0] + "' → '" + a[1] + "' ("
|
|
+ std::to_string(r.parts) + " part(s), "
|
|
+ std::to_string(r.signals) + " signal(s))");
|
|
return true;
|
|
}
|
|
if (cmd == "verify") {
|
|
render_verify();
|
|
return true;
|
|
}
|
|
if (cmd == "export") {
|
|
if (!need(1)) return false;
|
|
ExportFormat fmt;
|
|
if (!export_format_from_path(a[0], fmt)) {
|
|
emit("export: unknown extension (use .csv or .ods): " + a[0]); return false;
|
|
}
|
|
ExportResult r = export_connections(sys_.get(), a[0], fmt);
|
|
if (!r.ok) { emit("export failed: " + r.error); return false; }
|
|
emit("exported " + std::to_string(r.rows) + " row(s) to " + a[0]);
|
|
return true;
|
|
}
|
|
if (cmd == "save") {
|
|
if (!need(1)) return false;
|
|
std::string err;
|
|
if (!save_system(sys_.get(), a[0], err)) { emit("save failed: " + err); return false; }
|
|
emit("saved to " + a[0]);
|
|
return true;
|
|
}
|
|
if (cmd == "restore") {
|
|
if (!need(1)) return false;
|
|
std::string err;
|
|
System *fresh = restore_system(a[0], err);
|
|
if (!fresh) { emit("restore failed: " + err); return false; }
|
|
sys_.reset(fresh);
|
|
emit("restored from " + a[0] + " ("
|
|
+ std::to_string(sys_->modules()->size()) + " module(s), "
|
|
+ std::to_string(sys_->connections()->size()) + " connection(s))");
|
|
return true;
|
|
}
|
|
if (cmd == "source") {
|
|
if (!need(1)) return false;
|
|
bool opened;
|
|
int n = run_file(a[0], depth + 1, top, opened);
|
|
if (!opened) { emit("source: cannot open " + a[0]); return false; }
|
|
emit("source: " + a[0] + " (" + std::to_string(n) + " line(s))");
|
|
return true;
|
|
}
|
|
|
|
emit("script: unsupported command '" + cmd + "'");
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<System> &sys_;
|
|
std::ostream &out_;
|
|
std::map<std::string, std::string> vars_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
ScriptResult run_script(std::unique_ptr<System> &sys, const std::string &path,
|
|
std::ostream &out)
|
|
{
|
|
ScriptResult r;
|
|
Runner runner(sys, out);
|
|
bool opened = false;
|
|
int n = runner.run_file(path, 0, r, opened);
|
|
if (!opened) {
|
|
r.error = "cannot open " + path;
|
|
return r;
|
|
}
|
|
r.ok = true;
|
|
r.lines = n;
|
|
return r;
|
|
}
|
|
|
|
} // namespace app
|