25static void check(
bool ok,
const std::string& what) {
26 std::cout << (
ok ?
" ok " :
" FAIL ") << what << std::endl;
37 {
"GSE_HIGH_PRESS_CONTROL",
"GSE High Press Control"},
38 {
"GSE_HIGH_PRESS_VENT",
"GSE High Press Vent"},
39 {
"FUEL_VENT",
"Fuel Vent"},
40 {
"FUEL_MAIN",
"Fuel Main"},
41 {
"FUEL_UPSTREAM",
"Fuel Upstream"},
44 {
"GN2_HIGH",
"GN2 High"},
45 {
"GN2_REGULATED",
"GN2 Regulated"},
46 {
"CHAMBER",
"Chamber"},
47 {
"FUEL_UPSTREAM",
"Fuel Upstream"},
50 {
"PRESS_STANDBY",
"Press Standby"}, {
"IDLE",
"Idle"}, {
"VENT",
"Vent"},
51 {
"FUEL_VENT",
"Fuel Vent"}, {
"FIRE",
"Fire"},
54 t.allowed_transitions = {
"PRESS_STANDBY",
"IDLE",
"VENT",
"FUEL_VENT"};
72static const std::vector<Accept>
kAccept = {
74 {
"vent pulse (motivating example 1)",
75 "open_valve(GSE_HIGH_PRESS_VENT)\n"
77 "close_valve(GSE_HIGH_PRESS_VENT)\n"
78 "transition_to(PRESS_STANDBY)\n",
79 "(seq (open GSE_HIGH_PRESS_VENT) (delay 0.5) (close GSE_HIGH_PRESS_VENT) "
80 "(goto PRESS_STANDBY))"},
82 {
"COPV press (motivating example 2)",
83 "target = 0.9 * pressure(GN2_HIGH)\n"
84 "while pressure(GN2_REGULATED) < target:\n"
85 " open_valve(GSE_HIGH_PRESS_CONTROL)\n"
87 " close_valve(GSE_HIGH_PRESS_CONTROL)\n"
89 "transition_to(PRESS_STANDBY)\n",
90 "(seq (set target (* 0.9 (pressure GN2_HIGH))) "
91 "(while (< (pressure GN2_REGULATED) target) "
92 "(seq (open GSE_HIGH_PRESS_CONTROL) (delay 0.2) (close GSE_HIGH_PRESS_CONTROL) (delay 0.2))) "
93 "(goto PRESS_STANDBY))"},
96 {
"subtraction is left-associative",
"a = 1\nb = 2\nc = 3\nd = a - b - c\ndelay(1)\n",
97 "(seq (set a 1) (set b 2) (set c 3) (set d (- (- a b) c)) (delay 1))"},
99 {
"multiplication binds tighter than addition",
"a = 1\nb = 2\nc = 3\nd = a + b * c\ndelay(1)\n",
100 "(seq (set a 1) (set b 2) (set c 3) (set d (+ a (* b c))) (delay 1))"},
102 {
"unary minus binds tighter than multiplication",
"x = 2\ny = 3\nz = -x * y\ndelay(1)\n",
103 "(seq (set x 2) (set y 3) (set z (* (neg x) y)) (delay 1))"},
105 {
"not binds looser than comparison, tighter than and",
106 "a = 1\nb = 2\nif not a < b and a == 1:\n delay(1)\n",
107 "(seq (set a 1) (set b 2) (if (and (not (< a b)) (== a 1)) (seq (delay 1))))"},
109 {
"and binds tighter than or",
"a = 1\nb = 2\nc = 3\nif a or b and c:\n delay(1)\n",
110 "(seq (set a 1) (set b 2) (set c 3) (if (or a (and b c)) (seq (delay 1))))"},
112 {
"parentheses override precedence",
"a = 1\nb = 2\nc = 3\nd = (a + b) * c\ndelay(1)\n",
113 "(seq (set a 1) (set b 2) (set c 3) (set d (* (+ a b) c)) (delay 1))"},
116 {
"elif desugars to a nested if in the else arm",
117 "if pressure(CHAMBER) > 500:\n"
118 " transition_to(VENT)\n"
119 "elif pressure(CHAMBER) > 100:\n"
120 " transition_to(IDLE)\n"
122 " transition_to(PRESS_STANDBY)\n",
123 "(seq (if (> (pressure CHAMBER) 500) (seq (goto VENT)) "
124 "(seq (if (> (pressure CHAMBER) 100) (seq (goto IDLE)) (seq (goto PRESS_STANDBY))))))"},
126 {
"conditional transition — the point of the feature",
127 "if pressure(GN2_HIGH) > 4000:\n transition_to(FUEL_VENT)\nelse:\n "
128 "transition_to(VENT)\n",
129 "(seq (if (> (pressure GN2_HIGH) 4000) (seq (goto FUEL_VENT)) (seq (goto VENT))))"},
131 {
"comments and blank lines carry no structure",
132 "# lead-in\n\nopen_valve(FUEL_VENT) # trailing\n\n\ndelay(1)\n",
133 "(seq (open FUEL_VENT) (delay 1))"},
135 {
"two-space indent is fine as long as it is consistent",
"while elapsed() < 5:\n delay(0.1)\n",
136 "(seq (while (< (elapsed) 5) (seq (delay 0.1))))"},
139 "while elapsed() < 10:\n"
140 " if pressure(CHAMBER) > 100:\n"
141 " open_valve(FUEL_VENT)\n"
143 "(seq (while (< (elapsed) 10) (seq (if (> (pressure CHAMBER) 100) (seq (open FUEL_VENT))) "
147 {
"FUEL_VENT resolves as a valve and as a state in the same script",
148 "open_valve(FUEL_VENT)\ndelay(0.5)\nclose_valve(FUEL_VENT)\ntransition_to(FUEL_VENT)\n",
149 "(seq (open FUEL_VENT) (delay 0.5) (close FUEL_VENT) (goto FUEL_VENT))"},
151 {
"FUEL_UPSTREAM resolves as a valve and as a sensor in the same script",
152 "if pressure(FUEL_UPSTREAM) > 100:\n open_valve(FUEL_UPSTREAM)\ndelay(1)\n",
153 "(seq (if (> (pressure FUEL_UPSTREAM) 100) (seq (open FUEL_UPSTREAM))) (delay 1))"},
155 {
"a loop whose every if-arm delays satisfies the delay rule",
156 "while elapsed() < 5:\n"
157 " if pressure(CHAMBER) > 100:\n"
161 "(seq (while (< (elapsed) 5) (seq (if (> (pressure CHAMBER) 100) (seq (delay 0.1)) "
162 "(seq (delay 0.2))))))"},
168 {
"tab indentation",
"while elapsed() < 5:\n\tdelay(1)\n",
Diag::TabIndent, 2},
173 {
"inconsistent indent unit",
176 {
"dedent to no enclosing block",
177 "while elapsed() < 5:\n if elapsed() > 1:\n delay(1)\n delay(2)\n",
Diag::BadDedent,
184 {
"unknown function in an expression",
"x = flowrate(FUEL_VENT)\ndelay(1)\n",
Diag::UnknownCall,
191 {
"a variable where a valve name belongs",
"v = 1\nopen_valve(v)\ndelay(1)\n",
199 {
"loop with no delay",
"while elapsed() < 5:\n open_valve(FUEL_VENT)\n",
201 {
"loop whose if-arm delays but has no else",
215static std::vector<Diagnostic>
run(
const std::string& src,
const SlugTables& t) {
218 return r.diagnostics;
225 std::cout <<
"── accept ──" << std::endl;
229 check(
false, std::string(
a.name) +
" — parse failed: " +
r.diagnostics[0].message +
230 " (line " + std::to_string(
r.diagnostics[0].line) +
")");
233 const auto issues =
validate(
r.program, t);
234 if (!issues.empty()) {
235 check(
false, std::string(
a.name) +
" — validate failed: " + issues[0].message);
240 check(
false, std::string(
a.name) +
" — AST shape");
241 std::cout <<
" want " <<
a.ast << std::endl;
242 std::cout <<
" got " << got << std::endl;
248 std::cout <<
"── reject ──" << std::endl;
250 const auto issues =
run(rj.src, t);
251 if (issues.empty()) {
252 check(
false, std::string(rj.name) +
" — accepted, but should have been refused");
256 if (d.
code != rj.code) {
257 check(
false, std::string(rj.name) +
" — wrong code: wanted " +
diag_name(rj.code) +
261 if (d.
line != rj.line) {
262 check(
false, std::string(rj.name) +
" — wrong line: wanted " + std::to_string(rj.line) +
263 ", got " + std::to_string(d.
line));
267 std::string(rj.name) +
" -> " +
diag_name(d.
code) +
" @ " + std::to_string(d.
line));
270 std::cout <<
"── caps ──" << std::endl;
272 std::string deep =
"";
273 std::string indent =
"";
275 deep += indent +
"while elapsed() < 5:\n";
278 deep += indent +
"delay(1)\n";
279 const auto issues =
run(deep, t);
285 for (uint32_t i = 0; i <
kMaxLines + 5; i++)
286 many +=
"delay(1)\n";
287 const auto issues =
run(many, t);
289 "more than " + std::to_string(
kMaxLines) +
" lines is refused");
293 const auto issues =
run(big, t);
295 "a script over " + std::to_string(
kMaxSourceBytes) +
" bytes is refused");
298 std::cout <<
"── slugify ──" << std::endl;
299 check(
slugify(
"GN2 High") ==
"GN2_HIGH",
"\"GN2 High\" -> GN2_HIGH");
300 check(
slugify(
"GSE High Press Control") ==
"GSE_HIGH_PRESS_CONTROL",
301 "\"GSE High Press Control\" -> GSE_HIGH_PRESS_CONTROL");
302 check(
slugify(
" Fuel Vent ") ==
"FUEL_VENT",
"whitespace is trimmed and collapsed");
303 check(
slugify(
"Chamber Mid PT 1") ==
"CHAMBER_MID_PT_1",
"digits survive");
306 check(!
is_valid_slug(
"FUEL-VENT"),
"a punctuated name is refused rather than mangled");
309 std::cout <<
"── diagnostics are useful ──" << std::endl;
311 const auto issues =
run(
"open_valve(FUEL_VNT)\ndelay(1)\n", t);
312 check(!issues.empty() && issues[0].message.find(
"FUEL_VENT") != std::string::npos,
313 "a near-miss actuator name suggests the real one");
319 check(
r.ok() &&
r.program.empty(),
"an empty script parses to an empty program");
322 std::cout << (
g_failures == 0 ?
"\nAll state-script checks passed.\n"
323 :
"\nFAILURES: " + std::to_string(
g_failures) +
"\n");
*brief create JSON pointer *sa reference_tokens const string_t & a
const char * diag_name(Diag d)
std::string print_canonical(const ScriptProgram &program)
A stable s-expression rendering of the parsed program.
constexpr uint32_t kMaxLines
std::string slugify(const std::string &name)
Canonical name -> slug: trim, uppercase, collapse internal whitespace runs to a single '_'.
ParseResult parse(const std::string &source)
Lex and parse, applying every check that does not need config.
bool is_valid_slug(const std::string &slug)
True when a slug is usable as a bare identifier: ^[A-Z][A-Z0-9_]*$.
constexpr uint32_t kMaxNestingDepth
std::vector< Diagnostic > validate(const ScriptProgram &program, const SlugTables &tables)
Config-dependent checks.
constexpr size_t kMaxSourceBytes
std::string message
Human wording, including the offending text.
What a script's slugs are allowed to name, built from the loaded config.
static const std::vector< Accept > kAccept
static void check(bool ok, const std::string &what)
static SlugTables tables()
The tables every case validates against.
static const std::vector< Reject > kReject