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>
83 lines
2.4 KiB
C++
83 lines
2.4 KiB
C++
#include <doctest/doctest.h>
|
|
|
|
#include "core/app/script.hpp"
|
|
#include "core/domain/modules.hpp"
|
|
#include "core/domain/system.hpp"
|
|
|
|
#include <cstdio>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
// app::run_script is pure core: it drives the app::* operations from a command
|
|
// script and writes their output to a stream. No UI.
|
|
|
|
TEST_CASE("run_script builds a system from a command script") {
|
|
const char *net = "test_script_in.net";
|
|
{
|
|
std::ofstream f(net);
|
|
f << "COMP: 'C1' 'J1'\n"
|
|
" Explicit Pin: '1' 'x' 'NETA'\n"
|
|
" Explicit Pin: '2' 'x' 'NETB'\n"
|
|
"COMP: 'C2' 'J2'\n"
|
|
" Explicit Pin: '1' 'x' 'NETA'\n"
|
|
" Explicit Pin: '2' 'x' 'NETB'\n";
|
|
}
|
|
const char *scr = "test_script_in.essim";
|
|
{
|
|
std::ofstream f(scr);
|
|
f << "# a comment\n"
|
|
"\n"
|
|
"set NET " << net << "\n"
|
|
"new\n"
|
|
"load M $NET mentor\n"
|
|
"set-signal-type M NETA power\n"
|
|
"verify\n";
|
|
}
|
|
|
|
std::unique_ptr<System> sys;
|
|
std::ostringstream out;
|
|
app::ScriptResult r = app::run_script(sys, scr, out);
|
|
|
|
CHECK(r.ok);
|
|
CHECK(r.errors == 0);
|
|
REQUIRE(sys != nullptr);
|
|
CHECK(sys->modules()->size() == 1);
|
|
|
|
const std::string log = out.str();
|
|
CHECK(log.find("loaded 'M'") != std::string::npos); // load ran
|
|
CHECK(log.find("signal type = power") != std::string::npos); // $NET expanded + set
|
|
CHECK(log.find("verify:") != std::string::npos); // verify ran
|
|
|
|
std::remove(net);
|
|
std::remove(scr);
|
|
}
|
|
|
|
TEST_CASE("run_script reports a missing top-level file") {
|
|
std::unique_ptr<System> sys;
|
|
std::ostringstream out;
|
|
app::ScriptResult r =
|
|
app::run_script(sys, "/nonexistent-xyz/none.essim", out);
|
|
CHECK_FALSE(r.ok);
|
|
CHECK(r.error.find("cannot open") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE("run_script flags an unsupported command and keeps going") {
|
|
const char *scr = "test_script_unsup.essim";
|
|
{
|
|
std::ofstream f(scr);
|
|
f << "new\nfrobnicate widgets\nnew\n";
|
|
}
|
|
std::unique_ptr<System> sys;
|
|
std::ostringstream out;
|
|
app::ScriptResult r = app::run_script(sys, scr, out);
|
|
|
|
CHECK(r.ok);
|
|
CHECK(r.errors == 1);
|
|
CHECK(out.str().find("unsupported command 'frobnicate'") != std::string::npos);
|
|
REQUIRE(sys != nullptr); // the two `new` lines still ran
|
|
|
|
std::remove(scr);
|
|
}
|