Icarus
Vehicle Simulation as a Transformable Computational Graph, built on Vulcan and Janus
Loading...
Searching...
No Matches
ConditionParser.hpp
Go to the documentation of this file.
1#pragma once
2
11
12#include <icarus/core/Error.hpp>
14
15#include <cctype>
16#include <memory>
17#include <stdexcept>
18#include <string>
19#include <string_view>
20#include <variant>
21#include <vector>
22
23namespace icarus {
24
25// ============================================================================
26// Tokens
27// ============================================================================
28
29enum class TokenType {
30 // Literals
31 Number, // 0.01, 100, -5.5
32 Identifier, // Vehicle.Nav.altitude
33
34 // Comparison operators
35 Less, // <
36 LessEqual, // <=
37 Greater, // >
39 Equal, // ==
40 NotEqual, // !=
41
42 // Boolean operators
43 And, // AND, &&
44 Or, // OR, ||
45 Not, // NOT, !
46
47 // Grouping
50
51 // End of input
53};
54
55struct Token {
57 std::string value;
58 std::size_t position = 0;
59
60 Token(TokenType t, std::string v, std::size_t pos = 0)
61 : type(t), value(std::move(v)), position(pos) {}
62};
63
64// ============================================================================
65// Condition Nodes (AST)
66// ============================================================================
67
68template <typename Scalar> class ConditionNode {
69 public:
70 virtual ~ConditionNode() = default;
71 [[nodiscard]] virtual bool Evaluate(const SignalRegistry<Scalar> &registry) const = 0;
72 [[nodiscard]] virtual std::string ToString() const = 0;
73};
74
76template <typename Scalar> class ComparisonNode : public ConditionNode<Scalar> {
77 public:
79
80 ComparisonNode(std::string lhs, Op op, std::variant<double, std::string> rhs)
81 : lhs_(std::move(lhs)), op_(op), rhs_(std::move(rhs)) {}
82
83 [[nodiscard]] bool Evaluate(const SignalRegistry<Scalar> &registry) const override {
84 double lhs_val = GetValue(registry, lhs_);
85 double rhs_val = std::holds_alternative<double>(rhs_)
86 ? std::get<double>(rhs_)
87 : GetValue(registry, std::get<std::string>(rhs_));
88
89 switch (op_) {
90 case Op::Less:
91 return lhs_val < rhs_val;
92 case Op::LessEqual:
93 return lhs_val <= rhs_val;
94 case Op::Greater:
95 return lhs_val > rhs_val;
97 return lhs_val >= rhs_val;
98 case Op::Equal:
99 return lhs_val == rhs_val;
100 case Op::NotEqual:
101 return lhs_val != rhs_val;
102 }
103 return false;
104 }
105
106 [[nodiscard]] std::string ToString() const override {
107 std::string op_str;
108 switch (op_) {
109 case Op::Less:
110 op_str = "<";
111 break;
112 case Op::LessEqual:
113 op_str = "<=";
114 break;
115 case Op::Greater:
116 op_str = ">";
117 break;
118 case Op::GreaterEqual:
119 op_str = ">=";
120 break;
121 case Op::Equal:
122 op_str = "==";
123 break;
124 case Op::NotEqual:
125 op_str = "!=";
126 break;
127 }
128 std::string rhs_str = std::holds_alternative<double>(rhs_)
129 ? std::to_string(std::get<double>(rhs_))
130 : std::get<std::string>(rhs_);
131 return lhs_ + " " + op_str + " " + rhs_str;
132 }
133
134 private:
135 std::string lhs_;
136 Op op_;
137 std::variant<double, std::string> rhs_;
138
139 [[nodiscard]] static double GetValue(const SignalRegistry<Scalar> &registry,
140 const std::string &name) {
141 // GetByName returns const Scalar&, convert to double for comparison
142 if constexpr (std::is_same_v<Scalar, double>) {
143 return registry.GetByName(name);
144 } else {
145 // For symbolic types, we need numeric evaluation
146 // This is a runtime check - symbolic conditions require numeric context
147 throw ConditionError("Cannot evaluate symbolic signals in conditions: " + name);
148 }
149 }
150};
151
153template <typename Scalar> class AndNode : public ConditionNode<Scalar> {
154 public:
155 AndNode(std::unique_ptr<ConditionNode<Scalar>> left,
156 std::unique_ptr<ConditionNode<Scalar>> right)
157 : left_(std::move(left)), right_(std::move(right)) {}
158
159 [[nodiscard]] bool Evaluate(const SignalRegistry<Scalar> &registry) const override {
160 return left_->Evaluate(registry) && right_->Evaluate(registry);
161 }
162
163 [[nodiscard]] std::string ToString() const override {
164 return "(" + left_->ToString() + " AND " + right_->ToString() + ")";
165 }
166
167 private:
168 std::unique_ptr<ConditionNode<Scalar>> left_;
169 std::unique_ptr<ConditionNode<Scalar>> right_;
170};
171
173template <typename Scalar> class OrNode : public ConditionNode<Scalar> {
174 public:
175 OrNode(std::unique_ptr<ConditionNode<Scalar>> left,
176 std::unique_ptr<ConditionNode<Scalar>> right)
177 : left_(std::move(left)), right_(std::move(right)) {}
178
179 [[nodiscard]] bool Evaluate(const SignalRegistry<Scalar> &registry) const override {
180 return left_->Evaluate(registry) || right_->Evaluate(registry);
181 }
182
183 [[nodiscard]] std::string ToString() const override {
184 return "(" + left_->ToString() + " OR " + right_->ToString() + ")";
185 }
186
187 private:
188 std::unique_ptr<ConditionNode<Scalar>> left_;
189 std::unique_ptr<ConditionNode<Scalar>> right_;
190};
191
193template <typename Scalar> class NotNode : public ConditionNode<Scalar> {
194 public:
195 explicit NotNode(std::unique_ptr<ConditionNode<Scalar>> operand)
196 : operand_(std::move(operand)) {}
197
198 [[nodiscard]] bool Evaluate(const SignalRegistry<Scalar> &registry) const override {
199 return !operand_->Evaluate(registry);
200 }
201
202 [[nodiscard]] std::string ToString() const override { return "NOT " + operand_->ToString(); }
203
204 private:
205 std::unique_ptr<ConditionNode<Scalar>> operand_;
206};
207
208// ============================================================================
209// Tokenizer
210// ============================================================================
211
213 public:
214 explicit Tokenizer(std::string_view input) : input_(input), pos_(0) {}
215
216 [[nodiscard]] std::vector<Token> Tokenize() {
217 std::vector<Token> tokens;
218
219 while (!AtEnd()) {
220 SkipWhitespace();
221 if (AtEnd())
222 break;
223
224 std::size_t start = pos_;
225 char c = Peek();
226
227 // Two-character operators
228 if (c == '<' && PeekNext() == '=') {
229 Advance();
230 Advance();
231 tokens.emplace_back(TokenType::LessEqual, "<=", start);
232 } else if (c == '>' && PeekNext() == '=') {
233 Advance();
234 Advance();
235 tokens.emplace_back(TokenType::GreaterEqual, ">=", start);
236 } else if (c == '=' && PeekNext() == '=') {
237 Advance();
238 Advance();
239 tokens.emplace_back(TokenType::Equal, "==", start);
240 } else if (c == '!' && PeekNext() == '=') {
241 Advance();
242 Advance();
243 tokens.emplace_back(TokenType::NotEqual, "!=", start);
244 } else if (c == '&' && PeekNext() == '&') {
245 Advance();
246 Advance();
247 tokens.emplace_back(TokenType::And, "&&", start);
248 } else if (c == '|' && PeekNext() == '|') {
249 Advance();
250 Advance();
251 tokens.emplace_back(TokenType::Or, "||", start);
252 }
253 // Single-character operators
254 else if (c == '<') {
255 Advance();
256 tokens.emplace_back(TokenType::Less, "<", start);
257 } else if (c == '>') {
258 Advance();
259 tokens.emplace_back(TokenType::Greater, ">", start);
260 } else if (c == '!') {
261 Advance();
262 tokens.emplace_back(TokenType::Not, "!", start);
263 } else if (c == '(') {
264 Advance();
265 tokens.emplace_back(TokenType::LeftParen, "(", start);
266 } else if (c == ')') {
267 Advance();
268 tokens.emplace_back(TokenType::RightParen, ")", start);
269 }
270 // Numbers (including negative)
271 else if (std::isdigit(c) || (c == '-' && std::isdigit(PeekNext()))) {
272 tokens.push_back(ScanNumber());
273 }
274 // Identifiers and keywords
275 else if (std::isalpha(c) || c == '_') {
276 tokens.push_back(ScanIdentifier());
277 } else {
278 throw ConditionError("Unexpected character '" + std::string(1, c) +
279 "' at position " + std::to_string(pos_));
280 }
281 }
282
283 tokens.emplace_back(TokenType::Eof, "", pos_);
284 return tokens;
285 }
286
287 private:
288 std::string_view input_;
289 std::size_t pos_;
290
291 [[nodiscard]] bool AtEnd() const { return pos_ >= input_.size(); }
292
293 [[nodiscard]] char Peek() const { return AtEnd() ? '\0' : input_[pos_]; }
294
295 [[nodiscard]] char PeekNext() const {
296 return (pos_ + 1 >= input_.size()) ? '\0' : input_[pos_ + 1];
297 }
298
299 char Advance() { return input_[pos_++]; }
300
301 void SkipWhitespace() {
302 while (!AtEnd() && std::isspace(Peek())) {
303 Advance();
304 }
305 }
306
307 [[nodiscard]] Token ScanNumber() {
308 std::size_t start = pos_;
309 std::string value;
310
311 // Optional negative sign
312 if (Peek() == '-') {
313 value += Advance();
314 }
315
316 // Integer part
317 while (!AtEnd() && std::isdigit(Peek())) {
318 value += Advance();
319 }
320
321 // Decimal part
322 if (Peek() == '.' && std::isdigit(PeekNext())) {
323 value += Advance(); // consume '.'
324 while (!AtEnd() && std::isdigit(Peek())) {
325 value += Advance();
326 }
327 }
328
329 // Scientific notation
330 if (Peek() == 'e' || Peek() == 'E') {
331 value += Advance();
332 if (Peek() == '+' || Peek() == '-') {
333 value += Advance();
334 }
335 while (!AtEnd() && std::isdigit(Peek())) {
336 value += Advance();
337 }
338 }
339
340 return Token(TokenType::Number, value, start);
341 }
342
343 [[nodiscard]] Token ScanIdentifier() {
344 std::size_t start = pos_;
345 std::string value;
346
347 // First character: letter or underscore
348 while (!AtEnd() && (std::isalnum(Peek()) || Peek() == '_' || Peek() == '.')) {
349 value += Advance();
350 }
351
352 // Check for keywords
353 if (value == "AND" || value == "and") {
354 return Token(TokenType::And, value, start);
355 }
356 if (value == "OR" || value == "or") {
357 return Token(TokenType::Or, value, start);
358 }
359 if (value == "NOT" || value == "not") {
360 return Token(TokenType::Not, value, start);
361 }
362
363 return Token(TokenType::Identifier, value, start);
364 }
365};
366
367// ============================================================================
368// Parser (Recursive Descent)
369// ============================================================================
370
376template <typename Scalar> class CompiledCondition {
377 public:
378 CompiledCondition() = default;
379 explicit CompiledCondition(std::unique_ptr<ConditionNode<Scalar>> root)
380 : root_(std::move(root)) {}
381
383 [[nodiscard]] bool Evaluate(const SignalRegistry<Scalar> &registry) const {
384 if (!root_) {
385 throw ConditionError("Cannot evaluate empty condition");
386 }
387 return root_->Evaluate(registry);
388 }
389
391 [[nodiscard]] bool IsValid() const { return root_ != nullptr; }
392
394 [[nodiscard]] std::string ToString() const { return root_ ? root_->ToString() : "<empty>"; }
395
396 private:
397 std::unique_ptr<ConditionNode<Scalar>> root_;
398};
399
411template <typename Scalar> class ConditionParser {
412 public:
420 [[nodiscard]] CompiledCondition<Scalar> Parse(const std::string &condition) {
421 Tokenizer tokenizer(condition);
422 tokens_ = tokenizer.Tokenize();
423 current_ = 0;
424
425 auto root = ParseOrExpr();
426
427 if (!IsAtEnd()) {
428 throw ConditionError("Unexpected token '" + Peek().value + "' at position " +
429 std::to_string(Peek().position));
430 }
431
432 return CompiledCondition<Scalar>(std::move(root));
433 }
434
435 private:
436 std::vector<Token> tokens_;
437 std::size_t current_ = 0;
438
439 [[nodiscard]] const Token &Peek() const { return tokens_[current_]; }
440
441 [[nodiscard]] const Token &Previous() const { return tokens_[current_ - 1]; }
442
443 [[nodiscard]] bool IsAtEnd() const { return Peek().type == TokenType::Eof; }
444
445 Token Advance() {
446 if (!IsAtEnd())
447 current_++;
448 return Previous();
449 }
450
451 [[nodiscard]] bool Check(TokenType type) const {
452 if (IsAtEnd())
453 return false;
454 return Peek().type == type;
455 }
456
457 bool Match(TokenType type) {
458 if (Check(type)) {
459 Advance();
460 return true;
461 }
462 return false;
463 }
464
465 Token Consume(TokenType type, const std::string &message) {
466 if (Check(type))
467 return Advance();
468 throw ConditionError(message + " at position " + std::to_string(Peek().position));
469 }
470
471 // or_expr -> and_expr (OR and_expr)*
472 [[nodiscard]] std::unique_ptr<ConditionNode<Scalar>> ParseOrExpr() {
473 auto left = ParseAndExpr();
474
475 while (Match(TokenType::Or)) {
476 auto right = ParseAndExpr();
477 left = std::make_unique<OrNode<Scalar>>(std::move(left), std::move(right));
478 }
479
480 return left;
481 }
482
483 // and_expr -> not_expr (AND not_expr)*
484 [[nodiscard]] std::unique_ptr<ConditionNode<Scalar>> ParseAndExpr() {
485 auto left = ParseNotExpr();
486
487 while (Match(TokenType::And)) {
488 auto right = ParseNotExpr();
489 left = std::make_unique<AndNode<Scalar>>(std::move(left), std::move(right));
490 }
491
492 return left;
493 }
494
495 // not_expr -> NOT not_expr | primary
496 [[nodiscard]] std::unique_ptr<ConditionNode<Scalar>> ParseNotExpr() {
497 if (Match(TokenType::Not)) {
498 auto operand = ParseNotExpr();
499 return std::make_unique<NotNode<Scalar>>(std::move(operand));
500 }
501 return ParsePrimary();
502 }
503
504 // primary -> comparison | '(' expression ')'
505 [[nodiscard]] std::unique_ptr<ConditionNode<Scalar>> ParsePrimary() {
506 if (Match(TokenType::LeftParen)) {
507 auto expr = ParseOrExpr();
508 Consume(TokenType::RightParen, "Expected ')' after expression");
509 return expr;
510 }
511
512 return ParseComparison();
513 }
514
515 // comparison -> identifier ('<'|'<='|'>'|'>='|'=='|'!=') (number | identifier)
516 [[nodiscard]] std::unique_ptr<ConditionNode<Scalar>> ParseComparison() {
517 Token lhs = Consume(TokenType::Identifier, "Expected signal name");
518
519 typename ComparisonNode<Scalar>::Op op;
520 if (Match(TokenType::Less)) {
522 } else if (Match(TokenType::LessEqual)) {
524 } else if (Match(TokenType::Greater)) {
526 } else if (Match(TokenType::GreaterEqual)) {
528 } else if (Match(TokenType::Equal)) {
530 } else if (Match(TokenType::NotEqual)) {
532 } else {
533 throw ConditionError("Expected comparison operator at position " +
534 std::to_string(Peek().position));
535 }
536
537 std::variant<double, std::string> rhs;
538 if (Match(TokenType::Number)) {
539 rhs = std::stod(Previous().value);
540 } else if (Match(TokenType::Identifier)) {
541 rhs = Previous().value;
542 } else {
543 throw ConditionError("Expected number or signal name at position " +
544 std::to_string(Peek().position));
545 }
546
547 return std::make_unique<ComparisonNode<Scalar>>(lhs.value, op, std::move(rhs));
548 }
549};
550
551} // namespace icarus
Consolidated error handling for Icarus.
Signal Registry (Backplane) for Icarus.
AndNode(std::unique_ptr< ConditionNode< Scalar > > left, std::unique_ptr< ConditionNode< Scalar > > right)
Definition ConditionParser.hpp:155
std::string ToString() const override
Definition ConditionParser.hpp:163
bool Evaluate(const SignalRegistry< Scalar > &registry) const override
Definition ConditionParser.hpp:159
ComparisonNode(std::string lhs, Op op, std::variant< double, std::string > rhs)
Definition ConditionParser.hpp:80
Op
Definition ConditionParser.hpp:78
@ NotEqual
Definition ConditionParser.hpp:78
@ Less
Definition ConditionParser.hpp:78
@ GreaterEqual
Definition ConditionParser.hpp:78
@ Greater
Definition ConditionParser.hpp:78
@ LessEqual
Definition ConditionParser.hpp:78
@ Equal
Definition ConditionParser.hpp:78
bool Evaluate(const SignalRegistry< Scalar > &registry) const override
Definition ConditionParser.hpp:83
std::string ToString() const override
Definition ConditionParser.hpp:106
Compiled condition ready for evaluation.
Definition ConditionParser.hpp:376
std::string ToString() const
Get string representation.
Definition ConditionParser.hpp:394
CompiledCondition(std::unique_ptr< ConditionNode< Scalar > > root)
Definition ConditionParser.hpp:379
bool Evaluate(const SignalRegistry< Scalar > &registry) const
Evaluate the condition against the registry.
Definition ConditionParser.hpp:383
bool IsValid() const
Check if condition is valid (has been parsed).
Definition ConditionParser.hpp:391
Condition parsing and evaluation errors.
Definition Error.hpp:377
Definition ConditionParser.hpp:68
virtual bool Evaluate(const SignalRegistry< Scalar > &registry) const =0
virtual std::string ToString() const =0
virtual ~ConditionNode()=default
Parser for condition expressions.
Definition ConditionParser.hpp:411
CompiledCondition< Scalar > Parse(const std::string &condition)
Parse a condition string.
Definition ConditionParser.hpp:420
NotNode(std::unique_ptr< ConditionNode< Scalar > > operand)
Definition ConditionParser.hpp:195
std::string ToString() const override
Definition ConditionParser.hpp:202
bool Evaluate(const SignalRegistry< Scalar > &registry) const override
Definition ConditionParser.hpp:198
OrNode(std::unique_ptr< ConditionNode< Scalar > > left, std::unique_ptr< ConditionNode< Scalar > > right)
Definition ConditionParser.hpp:175
bool Evaluate(const SignalRegistry< Scalar > &registry) const override
Definition ConditionParser.hpp:179
std::string ToString() const override
Definition ConditionParser.hpp:183
Central registry for all simulation signals.
Definition Registry.hpp:37
const Scalar & GetByName(const std::string &name) const
Get signal value by name (slow path, for debugging).
Definition Registry.hpp:434
Definition ConditionParser.hpp:212
std::vector< Token > Tokenize()
Definition ConditionParser.hpp:216
Tokenizer(std::string_view input)
Definition ConditionParser.hpp:214
Definition AggregationTypes.hpp:13
TokenType
Definition ConditionParser.hpp:29
@ NotEqual
Definition ConditionParser.hpp:40
@ Less
Definition ConditionParser.hpp:35
@ Identifier
Definition ConditionParser.hpp:32
@ RightParen
Definition ConditionParser.hpp:49
@ Or
Definition ConditionParser.hpp:44
@ Eof
Definition ConditionParser.hpp:52
@ GreaterEqual
Definition ConditionParser.hpp:38
@ Greater
Definition ConditionParser.hpp:37
@ Not
Definition ConditionParser.hpp:45
@ Number
Definition ConditionParser.hpp:31
@ And
Definition ConditionParser.hpp:43
@ LessEqual
Definition ConditionParser.hpp:36
@ LeftParen
Definition ConditionParser.hpp:48
@ Equal
Definition ConditionParser.hpp:39
Definition ConditionParser.hpp:55
Token(TokenType t, std::string v, std::size_t pos=0)
Definition ConditionParser.hpp:60
std::size_t position
Definition ConditionParser.hpp:58
TokenType type
Definition ConditionParser.hpp:56
std::string value
Definition ConditionParser.hpp:57