STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
Parser.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <set>
3
4#include "Lexer.hpp"
6
7namespace fsw {
8namespace script {
9
10const char* ns_name(Ns ns) {
11 switch (ns) {
12 case Ns::Actuator:
13 return "actuator";
14 case Ns::PtSensor:
15 return "sensor";
16 case Ns::State:
17 return "state";
18 }
19 return "?";
20}
21
22const char* op_name(Op op) {
23 switch (op) {
24 case Op::Neg:
25 return "neg";
26 case Op::Not:
27 return "not";
28 case Op::Add:
29 return "+";
30 case Op::Sub:
31 return "-";
32 case Op::Mul:
33 return "*";
34 case Op::Div:
35 return "/";
36 case Op::Lt:
37 return "<";
38 case Op::Le:
39 return "<=";
40 case Op::Gt:
41 return ">";
42 case Op::Ge:
43 return ">=";
44 case Op::Eq:
45 return "==";
46 case Op::Ne:
47 return "!=";
48 case Op::And:
49 return "and";
50 case Op::Or:
51 return "or";
52 }
53 return "?";
54}
55
56const char* diag_name(Diag d) {
57 switch (d) {
58 case Diag::TabIndent:
59 return "E_TAB_INDENT";
61 return "E_BAD_INDENT_UNIT";
62 case Diag::BadDedent:
63 return "E_BAD_DEDENT";
65 return "E_UNEXPECTED_INDENT";
67 return "E_UNEXPECTED_CHAR";
68 case Diag::BadNumber:
69 return "E_BAD_NUMBER";
71 return "E_EXPECTED_COLON";
73 return "E_EXPECTED_LPAREN";
75 return "E_EXPECTED_RPAREN";
77 return "E_EXPECTED_NEWLINE";
79 return "E_EXPECTED_NAME";
81 return "E_EXPECTED_EXPR";
83 return "E_EXPECTED_BLOCK";
85 return "E_UNKNOWN_CALL";
87 return "E_CHAINED_COMPARISON";
89 return "E_CALL_NOT_A_STATEMENT";
91 return "E_ASSIGN_TO_CALL";
93 return "E_PROGRAM_TOO_LARGE";
95 return "E_TOO_MANY_LINES";
96 case Diag::TooDeep:
97 return "E_TOO_DEEP";
99 return "E_TOO_MANY_STATEMENTS";
101 return "E_TOO_MANY_VARIABLES";
103 return "E_EXPR_TOO_DEEP";
105 return "E_DELAY_NOT_POSITIVE";
107 return "E_LOOP_WITHOUT_DELAY";
109 return "E_VAR_USED_BEFORE_ASSIGN";
111 return "E_DIVIDE_BY_ZERO_LITERAL";
113 return "E_UNKNOWN_ACTUATOR";
115 return "E_UNKNOWN_SENSOR";
117 return "E_UNKNOWN_STATE";
119 return "E_TRANSITION_NOT_ALLOWED";
121 return "E_VAR_SHADOWS_SLUG";
122 }
123 return "E_UNKNOWN";
124}
125
126namespace {
127
128/** The builtin call names. A bare Ident in call position that is not one of these is refused, so a
129 * typo becomes an error rather than a silently ignored statement. */
130constexpr const char* kOpenValve = "open_valve";
131constexpr const char* kCloseValve = "close_valve";
132constexpr const char* kDelay = "delay";
133constexpr const char* kTransitionTo = "transition_to";
134constexpr const char* kPressure = "pressure";
135constexpr const char* kElapsed = "elapsed";
136
137bool isBuiltin(const std::string& s) {
138 return s == kOpenValve || s == kCloseValve || s == kDelay || s == kTransitionTo ||
139 s == kPressure || s == kElapsed;
140}
141
142struct Parser {
143 const std::vector<Token>& toks;
145 size_t p = 0;
146 bool failed = false;
147 uint32_t depth = 0;
148 /** Variables assigned somewhere at or before the current point, for use-before-assign.
149 * Deliberately flow-insensitive: a variable first assigned inside an `if` counts as assigned
150 * afterwards. Being stricter would reject readable scripts; being looser would let a NaN
151 * reach a comparison that decides whether a valve opens. */
152 std::set<std::string> assigned;
153
154 explicit Parser(const std::vector<Token>& t) : toks(t) {
155 }
156
157 const Token& cur() const {
158 return toks[std::min(p, toks.size() - 1)];
159 }
160 bool at(Tok k) const {
161 return cur().kind == k;
162 }
163 const Token& advance() {
164 const Token& t = toks[std::min(p, toks.size() - 1)];
165 if (p < toks.size() - 1)
166 p++;
167 return t;
168 }
169
170 void fail(Diag code, const Token& t, std::string msg) {
171 if (failed)
172 return;
173 failed = true;
174 out.diagnostics.push_back({code, t.line, t.col, std::move(msg)});
175 }
176
177 bool expect(Tok k, Diag code, const char* what) {
178 if (at(k)) {
179 advance();
180 return true;
181 }
182 fail(code, cur(), std::string("expected ") + what + ", found " + tok_name(cur().kind));
183 return false;
184 }
185
186 int32_t addExpr(Expr e) {
187 out.program.exprs.push_back(e);
188 return static_cast<int32_t>(out.program.exprs.size() - 1);
189 }
190 int32_t addStmt(Stmt s) {
191 out.program.stmts.push_back(s);
192 return static_cast<int32_t>(out.program.stmts.size() - 1);
193 }
194 int32_t internVar(const std::string& name) {
195 auto& v = out.program.variables;
196 for (size_t k = 0; k < v.size(); k++)
197 if (v[k] == name)
198 return static_cast<int32_t>(k);
199 v.push_back(name);
200 return static_cast<int32_t>(v.size() - 1);
201 }
202 int32_t addSlug(const Token& t, Ns ns) {
203 out.program.slugs.push_back(SlugRef{t.text, ns, t.line, t.col, t.len});
204 return static_cast<int32_t>(out.program.slugs.size() - 1);
205 }
206
207 // ── Statements ───────────────────────────────────────────────────────────────────────────
208
209 /** Parse statements until DEDENT/EOF, appending indices to block_items; returns [begin,end). */
210 std::pair<int32_t, int32_t> parseBlockBody() {
211 std::vector<int32_t> items;
212 while (!failed && !at(Tok::Dedent) && !at(Tok::Eof)) {
213 const int32_t s = parseStatement();
214 if (failed)
215 break;
216 items.push_back(s);
217 }
218 if (failed)
219 return {0, 0};
220 auto& bi = out.program.block_items;
221 const int32_t begin = static_cast<int32_t>(bi.size());
222 bi.insert(bi.end(), items.begin(), items.end());
223 return {begin, static_cast<int32_t>(bi.size())};
224 }
225
226 /** `: NEWLINE INDENT stmt+ DEDENT` */
227 std::pair<int32_t, int32_t> parseIndentedBlock() {
229 return {0, 0};
230 if (!expect(Tok::Newline, Diag::ExpectedNewline, "a line break after ':'"))
231 return {0, 0};
232 if (!at(Tok::Indent)) {
233 fail(Diag::ExpectedBlock, cur(), "expected an indented block after ':'");
234 return {0, 0};
235 }
236 advance(); // INDENT
237 depth++;
238 out.program.max_depth = std::max(out.program.max_depth, depth);
239 if (depth > kMaxNestingDepth) {
241 "nested " + std::to_string(depth) + " deep; the limit is " +
242 std::to_string(kMaxNestingDepth));
243 return {0, 0};
244 }
245 const auto range = parseBlockBody();
246 if (failed)
247 return {0, 0};
248 depth--;
249 if (!at(Tok::Dedent)) {
250 fail(Diag::ExpectedBlock, cur(), "block did not close as expected");
251 return {0, 0};
252 }
253 advance(); // DEDENT
254 if (range.first == range.second) {
256 "an indented block must contain at least one statement");
257 return {0, 0};
258 }
259 return range;
260 }
261
262 int32_t parseStatement() {
263 if (out.program.stmts.size() >= kMaxStatements) {
265 "more than " + std::to_string(kMaxStatements) + " statements");
266 return -1;
267 }
268 if (at(Tok::KwIf))
269 return parseIf();
270 if (at(Tok::KwWhile))
271 return parseWhile();
272 if (at(Tok::Indent)) {
273 fail(Diag::UnexpectedIndent, cur(), "unexpected indent — nothing here opens a block");
274 return -1;
275 }
276 return parseSimple();
277 }
278
279 int32_t parseIf() {
280 const Token kw = advance(); // if / elif
281 const int32_t cond = parseExpr();
282 if (failed)
283 return -1;
284 const auto body = parseIndentedBlock();
285 if (failed)
286 return -1;
287
288 Stmt s;
289 s.kind = StmtKind::If;
290 s.line = kw.line;
291 s.col = kw.col;
292 s.expr = cond;
293 s.body_begin = body.first;
294 s.body_end = body.second;
295
296 // `elif` desugars to an else arm holding a single nested If, so the interpreter has one
297 // shape to walk rather than a chain it has to special-case.
298 if (at(Tok::KwElif)) {
299 const int32_t nested = parseIf();
300 if (failed)
301 return -1;
302 auto& bi = out.program.block_items;
303 const int32_t begin = static_cast<int32_t>(bi.size());
304 bi.push_back(nested);
305 s.else_begin = begin;
306 s.else_end = static_cast<int32_t>(bi.size());
307 } else if (at(Tok::KwElse)) {
308 advance();
309 const auto els = parseIndentedBlock();
310 if (failed)
311 return -1;
312 s.else_begin = els.first;
313 s.else_end = els.second;
314 }
315 return addStmt(s);
316 }
317
318 int32_t parseWhile() {
319 const Token kw = advance();
320 const int32_t cond = parseExpr();
321 if (failed)
322 return -1;
323 const auto body = parseIndentedBlock();
324 if (failed)
325 return -1;
326
327 Stmt s;
329 s.line = kw.line;
330 s.col = kw.col;
331 s.expr = cond;
332 s.body_begin = body.first;
333 s.body_end = body.second;
334 const int32_t idx = addStmt(s);
335
336 // A loop body with no delay() on ANY path is a busy-wait. Refusing it at parse time makes
337 // a spin unrepresentable rather than something the iteration budget has to catch at
338 // runtime with a valve already open — and it is trivially explainable to an operator: a
339 // loop has to wait for something.
340 if (!blockHasDelay(body.first, body.second)) {
342 "this loop's body never calls delay() — add one (e.g. delay(0.05)) so the loop "
343 "waits rather than spinning, and so its poll rate is visible where it is read");
344 return -1;
345 }
346 return idx;
347 }
348
349 bool blockHasDelay(int32_t begin, int32_t end) const {
350 for (int32_t k = begin; k < end; k++) {
351 const Stmt& s = out.program.stmts[out.program.block_items[k]];
352 if (s.kind == StmtKind::Delay)
353 return true;
354 // A nested `while` must itself contain a delay (checked when it was parsed), so it
355 // counts. An `if` only counts when EVERY arm delays — an else-less if does not.
356 if (s.kind == StmtKind::While)
357 return true;
358 if (s.kind == StmtKind::If) {
359 const bool then_ok = blockHasDelay(s.body_begin, s.body_end);
360 const bool has_else = s.else_begin >= 0;
361 const bool else_ok = has_else && blockHasDelay(s.else_begin, s.else_end);
362 if (then_ok && else_ok)
363 return true;
364 }
365 }
366 return false;
367 }
368
369 int32_t parseSimple() {
370 if (!at(Tok::Ident)) {
372 std::string("expected a statement, found ") + tok_name(cur().kind));
373 return -1;
374 }
375 const Token name = advance();
376
377 if (at(Tok::Assign)) {
378 if (isBuiltin(name.text)) {
380 "'" + name.text + "' is a built-in and cannot be assigned to");
381 return -1;
382 }
383 advance();
384 const int32_t value = parseExpr();
385 if (failed)
386 return -1;
387 if (!expect(Tok::Newline, Diag::ExpectedNewline, "a line break"))
388 return -1;
389 if (out.program.variables.size() >= kMaxVariables &&
390 std::find(out.program.variables.begin(), out.program.variables.end(), name.text) ==
391 out.program.variables.end()) {
393 "more than " + std::to_string(kMaxVariables) + " variables");
394 return -1;
395 }
396 Stmt s;
398 s.line = name.line;
399 s.col = name.col;
400 s.var = internVar(name.text);
401 s.expr = value;
402 assigned.insert(name.text);
403 return addStmt(s);
404 }
405
406 if (!at(Tok::LParen)) {
408 "expected '=' or '(' after '" + name.text + "', found " + tok_name(cur().kind));
409 return -1;
410 }
411
412 StmtKind kind;
413 Ns ns;
414 if (name.text == kOpenValve) {
415 kind = StmtKind::OpenValve;
416 ns = Ns::Actuator;
417 } else if (name.text == kCloseValve) {
419 ns = Ns::Actuator;
420 } else if (name.text == kTransitionTo) {
422 ns = Ns::State;
423 } else if (name.text == kDelay) {
424 return parseDelay(name);
425 } else if (name.text == kPressure || name.text == kElapsed) {
427 "'" + name.text + "' produces a value and cannot stand alone as a statement");
428 return -1;
429 } else {
431 "unknown command '" + name.text +
432 "' — the commands are open_valve, close_valve, delay and transition_to");
433 return -1;
434 }
435
436 advance(); // '('
437 if (!at(Tok::Ident)) {
439 std::string("expected a bare ") + ns_name(ns) + " name here (e.g. " +
440 (ns == Ns::State ? "PRESS_STANDBY" : "FUEL_VENT") + "), found " +
441 tok_name(cur().kind));
442 return -1;
443 }
444 const Token slug = advance();
445 if (isBuiltin(slug.text)) {
446 fail(Diag::ExpectedName, slug, "'" + slug.text + "' is a built-in, not a name");
447 return -1;
448 }
450 return -1;
451 if (!expect(Tok::Newline, Diag::ExpectedNewline, "a line break"))
452 return -1;
453
454 Stmt s;
455 s.kind = kind;
456 s.line = name.line;
457 s.col = name.col;
458 s.slug = addSlug(slug, ns);
459 return addStmt(s);
460 }
461
462 int32_t parseDelay(const Token& name) {
463 advance(); // '('
464 const int32_t e = parseExpr();
465 if (failed)
466 return -1;
468 return -1;
469 if (!expect(Tok::Newline, Diag::ExpectedNewline, "a line break"))
470 return -1;
471
472 // A literal, non-positive delay is a spin the iteration budget would have to catch at
473 // runtime. It is knowable now, so it is refused now.
474 const Expr& ex = out.program.exprs[e];
475 if (ex.kind == ExprKind::Number && !(ex.number > 0.0)) {
477 "delay(" + std::to_string(ex.number) +
478 ") — a delay must be a positive number of "
479 "seconds");
480 return -1;
481 }
482
483 Stmt s;
485 s.line = name.line;
486 s.col = name.col;
487 s.expr = e;
488 return addStmt(s);
489 }
490
491 // ── Expressions ──────────────────────────────────────────────────────────────────────────
492
493 uint32_t edepth = 0;
494 struct EDepth {
496 explicit EDepth(Parser& pp) : p(pp) {
497 p.edepth++;
498 }
500 p.edepth--;
501 }
502 };
503
504 int32_t parseExpr() {
505 EDepth g(*this);
506 if (edepth > kMaxExprDepth) {
508 "expression nested deeper than " + std::to_string(kMaxExprDepth));
509 return -1;
510 }
511 return parseOr();
512 }
513
514 int32_t parseOr() {
515 int32_t lhs = parseAnd();
516 while (!failed && at(Tok::KwOr)) {
517 const Token t = advance();
518 const int32_t rhs = parseAnd();
519 if (failed)
520 return -1;
521 Expr e;
523 e.op = Op::Or;
524 e.lhs = lhs;
525 e.rhs = rhs;
526 e.line = t.line;
527 e.col = t.col;
528 lhs = addExpr(e);
529 }
530 return lhs;
531 }
532
533 int32_t parseAnd() {
534 int32_t lhs = parseNot();
535 while (!failed && at(Tok::KwAnd)) {
536 const Token t = advance();
537 const int32_t rhs = parseNot();
538 if (failed)
539 return -1;
540 Expr e;
542 e.op = Op::And;
543 e.lhs = lhs;
544 e.rhs = rhs;
545 e.line = t.line;
546 e.col = t.col;
547 lhs = addExpr(e);
548 }
549 return lhs;
550 }
551
552 int32_t parseNot() {
553 if (at(Tok::KwNot)) {
554 const Token t = advance();
555 const int32_t operand = parseNot();
556 if (failed)
557 return -1;
558 Expr e;
560 e.op = Op::Not;
561 e.lhs = operand;
562 e.line = t.line;
563 e.col = t.col;
564 return addExpr(e);
565 }
566 return parseComparison();
567 }
568
569 int32_t parseComparison() {
570 const int32_t lhs = parseSum();
571 if (failed)
572 return -1;
573 Op op;
574 switch (cur().kind) {
575 case Tok::Lt:
576 op = Op::Lt;
577 break;
578 case Tok::Le:
579 op = Op::Le;
580 break;
581 case Tok::Gt:
582 op = Op::Gt;
583 break;
584 case Tok::Ge:
585 op = Op::Ge;
586 break;
587 case Tok::EqEq:
588 op = Op::Eq;
589 break;
590 case Tok::Ne:
591 op = Op::Ne;
592 break;
593 default:
594 return lhs;
595 }
596 const Token t = advance();
597 const int32_t rhs = parseSum();
598 if (failed)
599 return -1;
600
601 // Non-associative on purpose. Python would read `a < b < c` as a chained comparison, which
602 // almost nobody reading a pressure condition under time pressure expects. Refuse it.
603 switch (cur().kind) {
604 case Tok::Lt:
605 case Tok::Le:
606 case Tok::Gt:
607 case Tok::Ge:
608 case Tok::EqEq:
609 case Tok::Ne:
611 "two comparisons in a row — write it as a and b, e.g. "
612 "(x > 100) and (x < 200)");
613 return -1;
614 default:
615 break;
616 }
617
618 Expr e;
620 e.op = op;
621 e.lhs = lhs;
622 e.rhs = rhs;
623 e.line = t.line;
624 e.col = t.col;
625 return addExpr(e);
626 }
627
628 int32_t parseSum() {
629 int32_t lhs = parseTerm();
630 while (!failed && (at(Tok::Plus) || at(Tok::Minus))) {
631 const Op op = at(Tok::Plus) ? Op::Add : Op::Sub;
632 const Token t = advance();
633 const int32_t rhs = parseTerm();
634 if (failed)
635 return -1;
636 Expr e;
638 e.op = op;
639 e.lhs = lhs;
640 e.rhs = rhs;
641 e.line = t.line;
642 e.col = t.col;
643 lhs = addExpr(e);
644 }
645 return lhs;
646 }
647
648 int32_t parseTerm() {
649 int32_t lhs = parseUnary();
650 while (!failed && (at(Tok::Star) || at(Tok::Slash))) {
651 const bool is_div = at(Tok::Slash);
652 const Token t = advance();
653 const int32_t rhs = parseUnary();
654 if (failed)
655 return -1;
656 if (is_div) {
657 const Expr& r = out.program.exprs[rhs];
658 if (r.kind == ExprKind::Number && r.number == 0.0) {
659 fail(Diag::DivideByZeroLiteral, t, "division by a literal zero");
660 return -1;
661 }
662 }
663 Expr e;
665 e.op = is_div ? Op::Div : Op::Mul;
666 e.lhs = lhs;
667 e.rhs = rhs;
668 e.line = t.line;
669 e.col = t.col;
670 lhs = addExpr(e);
671 }
672 return lhs;
673 }
674
675 int32_t parseUnary() {
676 if (at(Tok::Minus)) {
677 const Token t = advance();
678 const int32_t operand = parseUnary();
679 if (failed)
680 return -1;
681 // Fold a negated literal into a negative literal. Without this, -1 is
682 // Unary(Neg, Number(1)) and every "is this a non-positive constant?" check — delay()'s
683 // especially — looks straight past it, so delay(-1) parsed clean and only turned into a
684 // clamped 10 ms spin at runtime. A negative literal is knowable here, so it is folded
685 // here. Only literals fold: `-x` stays Unary(Neg, Var x).
686 if (operand >= 0 &&
687 out.program.exprs[static_cast<size_t>(operand)].kind == ExprKind::Number) {
688 Expr n = out.program.exprs[static_cast<size_t>(operand)];
689 n.number = -n.number;
690 n.line = t.line;
691 n.col = t.col;
692 if (static_cast<size_t>(operand) + 1 == out.program.exprs.size())
693 out.program.exprs.pop_back(); // the un-negated literal has no other referent
694 return addExpr(n);
695 }
696 Expr e;
698 e.op = Op::Neg;
699 e.lhs = operand;
700 e.line = t.line;
701 e.col = t.col;
702 return addExpr(e);
703 }
704 return parsePrimary();
705 }
706
707 int32_t parsePrimary() {
708 EDepth g(*this);
709 if (edepth > kMaxExprDepth) {
711 "expression nested deeper than " + std::to_string(kMaxExprDepth));
712 return -1;
713 }
714 if (at(Tok::Number)) {
715 const Token t = advance();
716 Expr e;
718 e.number = t.number;
719 e.line = t.line;
720 e.col = t.col;
721 return addExpr(e);
722 }
723 if (at(Tok::LParen)) {
724 advance();
725 const int32_t inner = parseExpr();
726 if (failed)
727 return -1;
729 return -1;
730 return inner;
731 }
732 if (at(Tok::Ident)) {
733 const Token t = advance();
734 if (t.text == kPressure) {
735 if (!expect(Tok::LParen, Diag::ExpectedLParen, "'(' after pressure"))
736 return -1;
737 if (!at(Tok::Ident)) {
739 std::string("expected a bare sensor name (e.g. GN2_HIGH), found ") +
740 tok_name(cur().kind));
741 return -1;
742 }
743 const Token slug = advance();
745 return -1;
746 Expr e;
748 e.slug = addSlug(slug, Ns::PtSensor);
749 e.line = t.line;
750 e.col = t.col;
751 return addExpr(e);
752 }
753 if (t.text == kElapsed) {
754 if (!expect(Tok::LParen, Diag::ExpectedLParen, "'(' after elapsed"))
755 return -1;
757 "')' — elapsed() takes no arguments"))
758 return -1;
759 Expr e;
761 e.line = t.line;
762 e.col = t.col;
763 return addExpr(e);
764 }
765 if (isBuiltin(t.text)) {
767 "'" + t.text +
768 "' does not produce a value and cannot appear in an expression");
769 return -1;
770 }
771 if (at(Tok::LParen)) {
773 "unknown function '" + t.text +
774 "' — the value-producing built-ins are pressure() and elapsed()");
775 return -1;
776 }
777 if (assigned.count(t.text) == 0) {
779 "'" + t.text + "' is read before it is ever assigned");
780 return -1;
781 }
782 Expr e;
784 e.var = internVar(t.text);
785 e.line = t.line;
786 e.col = t.col;
787 return addExpr(e);
788 }
790 std::string("expected a value, found ") + tok_name(cur().kind));
791 return -1;
792 }
793
794 void run() {
795 while (!failed && at(Tok::Newline))
796 advance();
797 const auto range = parseBlockBody();
798 if (failed)
799 return;
800 if (!at(Tok::Eof)) {
802 std::string("unexpected ") + tok_name(cur().kind) +
803 " after the end of the script");
804 return;
805 }
806 out.program.top_begin = range.first;
807 out.program.top_end = range.second;
808 }
809};
810
811} // namespace
812
813ParseResult parse(const std::string& source) {
815 LexResult lx = lex(source);
816 if (!lx.ok()) {
817 result.diagnostics = std::move(lx.diagnostics);
818 return result;
819 }
820 Parser ps(lx.tokens);
821 ps.run();
822 return std::move(ps.out);
823}
824
825// ─────────────────────────────────────────────────────────────────────────────────────────────
826// Canonical printer — the conformance corpus's shape check.
827// ─────────────────────────────────────────────────────────────────────────────────────────────
828namespace {
829
830void printExpr(const ScriptProgram& p, int32_t idx, std::string& out) {
831 if (idx < 0) {
832 out += "?";
833 return;
834 }
835 const Expr& e = p.exprs[static_cast<size_t>(idx)];
836 switch (e.kind) {
837 case ExprKind::Number: {
838 // Trim a trailing ".000000" so 0.5 prints as 0.5 and 2 prints as 2.
839 std::string s = std::to_string(e.number);
840 while (s.size() > 1 && s.back() == '0')
841 s.pop_back();
842 if (!s.empty() && s.back() == '.')
843 s.pop_back();
844 out += s;
845 break;
846 }
847 case ExprKind::Var:
848 out += p.variables[static_cast<size_t>(e.var)];
849 break;
851 out += "(pressure " + p.slugs[static_cast<size_t>(e.slug)].slug + ")";
852 break;
854 out += "(elapsed)";
855 break;
856 case ExprKind::Unary:
857 out += std::string("(") + op_name(e.op) + " ";
858 printExpr(p, e.lhs, out);
859 out += ")";
860 break;
861 case ExprKind::Binary:
862 out += std::string("(") + op_name(e.op) + " ";
863 printExpr(p, e.lhs, out);
864 out += " ";
865 printExpr(p, e.rhs, out);
866 out += ")";
867 break;
868 }
869}
870
871void printBlock(const ScriptProgram& p, int32_t begin, int32_t end, std::string& out);
872
873void printStmt(const ScriptProgram& p, int32_t idx, std::string& out) {
874 const Stmt& s = p.stmts[static_cast<size_t>(idx)];
875 switch (s.kind) {
876 case StmtKind::Assign:
877 out += "(set " + p.variables[static_cast<size_t>(s.var)] + " ";
878 printExpr(p, s.expr, out);
879 out += ")";
880 break;
882 out += "(open " + p.slugs[static_cast<size_t>(s.slug)].slug + ")";
883 break;
885 out += "(close " + p.slugs[static_cast<size_t>(s.slug)].slug + ")";
886 break;
887 case StmtKind::Delay:
888 out += "(delay ";
889 printExpr(p, s.expr, out);
890 out += ")";
891 break;
893 out += "(goto " + p.slugs[static_cast<size_t>(s.slug)].slug + ")";
894 break;
895 case StmtKind::If:
896 out += "(if ";
897 printExpr(p, s.expr, out);
898 out += " ";
899 printBlock(p, s.body_begin, s.body_end, out);
900 if (s.else_begin >= 0) {
901 out += " ";
902 printBlock(p, s.else_begin, s.else_end, out);
903 }
904 out += ")";
905 break;
906 case StmtKind::While:
907 out += "(while ";
908 printExpr(p, s.expr, out);
909 out += " ";
910 printBlock(p, s.body_begin, s.body_end, out);
911 out += ")";
912 break;
913 }
914}
915
916void printBlock(const ScriptProgram& p, int32_t begin, int32_t end, std::string& out) {
917 out += "(seq";
918 for (int32_t k = begin; k < end; k++) {
919 out += " ";
920 printStmt(p, p.block_items[static_cast<size_t>(k)], out);
921 }
922 out += ")";
923}
924
925} // namespace
926
927std::string print_canonical(const ScriptProgram& program) {
928 std::string out;
929 printBlock(program, program.top_begin, program.top_end, out);
930 return out;
931}
932
933} // namespace script
934} // namespace fsw
int32_t p
Definition flight_e2e.c:78
*brief append another JSON pointer at the end of this JSON pointer *sa ptr reference_tokens begin()
*brief create JSON pointer *sa reference_tokens end()
T & at(const key_type &key)
Definition json.hpp:19110
*brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer *sa const json_pointer & rhs
Definition json.hpp:13930
json_pointer result
Definition json.hpp:14064
void printExpr(const ScriptProgram &p, int32_t idx, std::string &out)
Definition Parser.cpp:830
bool isBuiltin(const std::string &s)
Definition Parser.cpp:137
constexpr const char * kOpenValve
The builtin call names.
Definition Parser.cpp:130
void printBlock(const ScriptProgram &p, int32_t begin, int32_t end, std::string &out)
Definition Parser.cpp:916
void printStmt(const ScriptProgram &p, int32_t idx, std::string &out)
Definition Parser.cpp:873
const char * diag_name(Diag d)
Definition Parser.cpp:56
constexpr uint32_t kMaxExprDepth
Ns
Which table a slug is resolved against.
std::string print_canonical(const ScriptProgram &program)
A stable s-expression rendering of the parsed program.
Definition Parser.cpp:927
const char * op_name(Op op)
Definition Parser.cpp:22
const char * tok_name(Tok t)
Definition Lexer.cpp:9
constexpr uint32_t kMaxVariables
constexpr uint32_t kMaxStatements
LexResult lex(const std::string &source)
Tokenize, emitting INDENT/DEDENT for block structure.
Definition Lexer.cpp:390
Diag
Diagnostic codes.
ParseResult parse(const std::string &source)
Lex and parse, applying every check that does not need config.
Definition Parser.cpp:813
constexpr uint32_t kMaxNestingDepth
const char * ns_name(Ns ns)
Definition Parser.cpp:10
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
bool ok() const
Definition Lexer.hpp:65
std::vector< Diagnostic > diagnostics
Definition Lexer.hpp:63
std::vector< Token > tokens
Definition Lexer.hpp:62
A parsed script, as a flat arena.
One slug reference, with its source span so a rename can rewrite exactly this token.
int32_t body_begin
If/While body, and If's else arm: [begin, end) into ScriptProgram::block_items.
int32_t expr
Assign value, Delay seconds, If/While condition.
int32_t slug
OpenValve/CloseValve/TransitionTo target.
int32_t var
Assign: index into ScriptProgram::variables.
uint32_t line
Definition Lexer.hpp:56
std::string text
Definition Lexer.hpp:54
bool blockHasDelay(int32_t begin, int32_t end) const
Definition Parser.cpp:349
std::pair< int32_t, int32_t > parseIndentedBlock()
: NEWLINE INDENT stmt+ DEDENT
Definition Parser.cpp:227
void fail(Diag code, const Token &t, std::string msg)
Definition Parser.cpp:170
bool expect(Tok k, Diag code, const char *what)
Definition Parser.cpp:177
int32_t internVar(const std::string &name)
Definition Parser.cpp:194
std::set< std::string > assigned
Variables assigned somewhere at or before the current point, for use-before-assign.
Definition Parser.cpp:152
std::pair< int32_t, int32_t > parseBlockBody()
Parse statements until DEDENT/EOF, appending indices to block_items; returns [begin,...
Definition Parser.cpp:210
static ArmGate g(kMe)
static boot_report_t r