STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
Validate.cpp
Go to the documentation of this file.
1#include <algorithm>
2
4
5namespace fsw {
6namespace script {
7
8namespace {
9
10/** A short "did you mean" for a slug that did not resolve. Cheap edit-distance-1-ish: same length
11 * with one character different, or a one-character insertion/deletion. Enough to catch the real
12 * typo (FUEL_VNT for FUEL_VENT) without pretending to be a spell checker. */
13std::string nearest(const std::string& want, const std::map<std::string, std::string>& table) {
14 auto close = [&](const std::string& a, const std::string& b) {
15 const size_t la = a.size(), lb = b.size();
16 if (la == lb) {
17 int diff = 0;
18 for (size_t i = 0; i < la; i++)
19 if (a[i] != b[i] && ++diff > 1)
20 return false;
21 return diff == 1;
22 }
23 if (la + 1 != lb && lb + 1 != la)
24 return false;
25 const std::string& s = (la < lb) ? a : b; // shorter
26 const std::string& l = (la < lb) ? b : a; // longer
27 size_t i = 0, j = 0;
28 bool skipped = false;
29 while (i < s.size() && j < l.size()) {
30 if (s[i] == l[j]) {
31 i++;
32 j++;
33 } else if (!skipped) {
34 skipped = true;
35 j++;
36 } else {
37 return false;
38 }
39 }
40 return true;
41 };
42 for (const auto& [slug, canonical] : table)
43 if (close(want, slug))
44 return slug;
45 return {};
46}
47
48std::string suffixFor(const std::string& want, const std::map<std::string, std::string>& table) {
49 const std::string near = nearest(want, table);
50 if (!near.empty())
51 return " — did you mean " + near + "?";
52 if (table.empty())
53 return "";
54 // No near match: name a couple of real options so the operator can see the spelling shape.
55 std::string s = " — known names include ";
56 int n = 0;
57 for (const auto& [slug, canonical] : table) {
58 if (n++ >= 3)
59 break;
60 if (n > 1)
61 s += ", ";
62 s += slug;
63 }
64 return s;
65}
66
67} // namespace
68
69std::vector<Diagnostic> validate(const ScriptProgram& program, const SlugTables& tables) {
70 std::vector<Diagnostic> out;
71
72 // ── Slugs resolve in the namespace their argument slot names ─────────────────────────────
73 //
74 // Positional, never by spelling. On the shipped server profile FUEL_VENT is both a valve and a
75 // state; open_valve(FUEL_VENT) and transition_to(FUEL_VENT) are both legitimate and mean
76 // different things. Checking each reference against only its own slot's table is what makes
77 // that work.
78 for (const SlugRef& ref : program.slugs) {
79 const std::map<std::string, std::string>* table = nullptr;
81 switch (ref.ns) {
82 case Ns::Actuator:
83 table = &tables.actuators;
85 break;
86 case Ns::PtSensor:
87 table = &tables.sensors;
89 break;
90 case Ns::State:
91 table = &tables.states;
92 code = Diag::UnknownState;
93 break;
94 }
95 if (table->count(ref.slug) == 0) {
96 out.push_back({code, ref.line, ref.col,
97 std::string("no ") + ns_name(ref.ns) + " named " + ref.slug +
98 suffixFor(ref.slug, *table)});
99 continue;
100 }
101 // Every transition_to target is checked against the matrix, not just the two configured
102 // fallbacks. A script that runs for 25 seconds and only THEN discovers it cannot leave is
103 // strictly worse than one that never starts.
104 if (ref.ns == Ns::State && tables.allowed_transitions.count(ref.slug) == 0) {
105 out.push_back({Diag::TransitionNotAllowed, ref.line, ref.col,
106 "this state is not allowed to transition to " + ref.slug +
107 " — state_transitions.csv does not permit it"});
108 }
109 }
110
111 // ── A variable must not be named like any slug in any namespace ──────────────────────────
112 //
113 // Positional resolution means `target = 1` beside `pressure(TARGET)` is technically
114 // unambiguous, but an operator reading it has to know the rule to see which is which. Refusing
115 // the overlap keeps the error messages honest and costs nothing: rename the variable.
116 for (const std::string& var : program.variables) {
117 const std::string slug = slugify(var);
118 const char* where = nullptr;
119 if (tables.actuators.count(slug))
120 where = "an actuator";
121 else if (tables.sensors.count(slug))
122 where = "a sensor";
123 else if (tables.states.count(slug))
124 where = "a state";
125 if (where == nullptr)
126 continue;
127 // Point at the variable's first appearance rather than line 0.
128 uint32_t line = 0, col = 0;
129 for (const Stmt& s : program.stmts) {
130 if (s.kind == StmtKind::Assign &&
131 program.variables[static_cast<size_t>(s.var)] == var) {
132 line = s.line;
133 col = s.col;
134 break;
135 }
136 }
137 out.push_back({Diag::VarShadowsSlug, line, col,
138 "variable '" + var + "' has the same name as " + where + " (" + slug +
139 ") — rename the variable"});
140 }
141
142 std::stable_sort(out.begin(), out.end(), [](const Diagnostic& a, const Diagnostic& b) {
143 if (a.line != b.line)
144 return a.line < b.line;
145 return a.col < b.col;
146 });
147 return out;
148}
149
150} // namespace script
151} // namespace fsw
TOML_PURE_INLINE_GETTER bool empty() const noexcept
Definition toml.hpp:8204
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
Definition json.hpp:13878
*brief create JSON pointer *sa reference_tokens const string_t & a
Definition json.hpp:13877
std::string suffixFor(const std::string &want, const std::map< std::string, std::string > &table)
Definition Validate.cpp:48
std::string nearest(const std::string &want, const std::map< std::string, std::string > &table)
A short "did you mean" for a slug that did not resolve.
Definition Validate.cpp:13
std::string slugify(const std::string &name)
Canonical name -> slug: trim, uppercase, collapse internal whitespace runs to a single '_'.
Definition Slug.cpp:8
Diag
Diagnostic codes.
const char * ns_name(Ns ns)
Definition Parser.cpp:10
std::vector< Diagnostic > validate(const ScriptProgram &program, const SlugTables &tables)
Config-dependent checks.
Definition Validate.cpp:69
Motorized ball valve position: raw ADC counts -> % open.
*brief user defined string literal for JSON values *sa std::size_t n
Definition json.hpp:24474
A parsed script, as a flat arena.
std::vector< Stmt > stmts
std::vector< std::string > variables
std::vector< SlugRef > slugs
Every slug reference in SOURCE ORDER, with spans.
One slug reference, with its source span so a rename can rewrite exactly this token.
What a script's slugs are allowed to name, built from the loaded config.
int32_t var
Assign: index into ScriptProgram::variables.
static bool near(double a, double b, double tol)
static int32_t diff(uint32_t a, uint32_t b)
static SlugTables tables()
The tables every case validates against.