Add a core script engine; wire File ▸ Run script in the wx GUI.

The wx frontend had no way to run essim command scripts (only the tui shell
did). Add a frontend-agnostic engine in core/app/script.{hpp,cpp}:
run_script(unique_ptr<System>&, path, ostream&) -> {ok, error, lines, errors}.
It parses essim scripts (# comments, blank lines, "quoted" args, set + $var /
${var} expansion, nested source with a depth guard) and dispatches the
scriptable, system-building commands — new, load, connect, set-connector-type,
set-signal-type, attach-bsdl, verify, export, save, restore — to the existing
app::* operations, writing their (TUI-identical) output to the stream.
Unsupported/interactive commands are reported and counted, execution continues.
System is taken by reference so new/restore can replace it.

wx gains File ▸ Run script…: pick a .essim, run it, echo the output into the
log and refresh. WxFrontend exposes system_ptr() for the engine.

tests/test_script.cpp: a full script (comment + set/$var + new + load + set-
signal-type + verify) builds the system and produces the expected log; missing
file reported; unsupported command flagged and skipped. 400 core assertions
green; tui + wx build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 21:49:05 +02:00
parent 184b0d306f
commit fc71cce647
6 changed files with 470 additions and 0 deletions

View File

@@ -6,6 +6,7 @@
#include "core/app/edit.hpp"
#include "core/app/export.hpp"
#include "core/app/load.hpp"
#include "core/app/script.hpp"
#include "core/app/verify.hpp"
#include "core/domain/analysis.hpp"
#include "core/domain/connect.hpp"
@@ -26,6 +27,7 @@
#include <algorithm>
#include <cctype>
#include <sstream>
#include <string>
#include <vector>
@@ -33,6 +35,7 @@ namespace {
enum {
ID_LOAD = wxID_HIGHEST + 1,
ID_RESTORE,
ID_RUN_SCRIPT,
ID_SAVE,
ID_EXPORT,
ID_SET_CONNECTOR_TYPE,
@@ -116,6 +119,7 @@ EssimFrame::EssimFrame(WxFrontend &fe)
auto *file = new wxMenu;
file->Append(ID_LOAD, "&Load module…\tCtrl-L");
file->Append(ID_RESTORE, "&Restore snapshot…\tCtrl-R");
file->Append(ID_RUN_SCRIPT, "&Run script…\tCtrl-U");
file->Append(ID_SAVE, "&Save snapshot…\tCtrl-S");
file->AppendSeparator();
file->Append(ID_EXPORT, "&Export connections…\tCtrl-E");
@@ -169,6 +173,7 @@ EssimFrame::EssimFrame(WxFrontend &fe)
Bind(wxEVT_MENU, &EssimFrame::OnLoad, this, ID_LOAD);
Bind(wxEVT_MENU, &EssimFrame::OnRestore, this, ID_RESTORE);
Bind(wxEVT_MENU, &EssimFrame::OnSave, this, ID_SAVE);
Bind(wxEVT_MENU, &EssimFrame::OnRunScript, this, ID_RUN_SCRIPT);
Bind(wxEVT_MENU, &EssimFrame::OnExport, this, ID_EXPORT);
Bind(wxEVT_MENU, &EssimFrame::OnSetConnectorType, this, ID_SET_CONNECTOR_TYPE);
Bind(wxEVT_MENU, &EssimFrame::OnAttachBsdl, this, ID_ATTACH_BSDL);
@@ -350,6 +355,32 @@ void EssimFrame::OnRestore(wxCommandEvent &) {
RebuildModelView();
}
void EssimFrame::OnRunScript(wxCommandEvent &) {
wxFileDialog dlg(this, "Run an essim script", "", "",
"essim scripts (*.essim)|*.essim|All files (*.*)|*.*",
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dlg.ShowModal() != wxID_OK) return;
fe_.ensure_system();
std::ostringstream out;
app::ScriptResult r =
app::run_script(fe_.system_ptr(), dlg.GetPath().utf8_string(), out);
if (!r.ok) {
Log("run script: " + wx(r.error));
wxMessageBox(wx(r.error), "Run script", wxOK | wxICON_ERROR, this);
return;
}
// Echo each line of the script's output into the log pane.
std::istringstream lines(out.str());
std::string line;
while (std::getline(lines, line)) Log(wx(line));
Log(wxString::Format("source: %s (%d line(s), %d error(s))",
dlg.GetPath(), r.lines, r.errors));
RebuildModelView();
}
void EssimFrame::OnSave(wxCommandEvent &) {
wxFileDialog dlg(this, "Save system snapshot", "", "system.essim",
"essim snapshots (*.essim)|*.essim|All files (*.*)|*.*",