BehaviorTree
Core Library to create and execute Behavior Trees
Loading...
Searching...
No Matches
operators.hpp
1/* Copyright (C) 2022-2025 Davide Faconti - All Rights Reserved
2*
3* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
4* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
5* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7*
8* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
10* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11*/
12
13#pragma once
14
15#include "behaviortree_cpp/scripting/any_types.hpp"
16#include "behaviortree_cpp/scripting/script_parser.hpp"
17
18#include <cmath>
19#include <memory>
20#include <string>
21#include <vector>
22
23// Naive implementation of an AST with simple evaluation function.
24namespace BT::Ast
25{
26using SimpleString = SafeAny::SimpleString;
27
28using expr_ptr = std::shared_ptr<struct ExprBase>;
29
30// extended string to number that consider enums and booleans
31inline double StringToDouble(const Any& value, const Environment& env)
32{
33 const auto str = value.cast<std::string>();
34 if(str == "true")
35 {
36 return 1.0;
37 }
38 if(str == "false")
39 {
40 return 0.0;
41 }
42 if(env.enums)
43 {
44 auto it = env.enums->find(str);
45 if(it != env.enums->end())
46 {
47 return it->second;
48 }
49 }
50 return value.cast<double>();
51}
52
53struct ExprBase
54{
55 using Ptr = std::shared_ptr<ExprBase>;
56
57 virtual ~ExprBase() = default;
58 virtual Any evaluate(Environment& env) const = 0;
59};
60
61inline std::string ErrorNotInit(const char* side, const char* op_str)
62{
63 return StrCat("The ", side, " operand of the operator [", op_str,
64 "] is not initialized");
65}
66
67struct ExprLiteral : ExprBase
68{
69 Any value;
70
71 ExprLiteral(Any v) : value(v)
72 {}
73
74 Any evaluate(Environment&) const override
75 {
76 return value;
77 }
78};
79
80struct ExprName : ExprBase
81{
82 std::string name;
83
84 explicit ExprName(std::string n) : name(std::move(n))
85 {}
86
87 Any evaluate(Environment& env) const override
88 {
89 //search first in the enums table
90 if(env.enums)
91 {
92 auto enum_ptr = env.enums->find(name);
93 if(enum_ptr != env.enums->end())
94 {
95 return Any(double(enum_ptr->second));
96 }
97 }
98 // search now in the variables table
99 auto any_ref = env.vars->getAnyLocked(name);
100 if(!any_ref)
101 {
102 throw RuntimeError(StrCat("Variable not found: ", name));
103 }
104 return *any_ref.get();
105 }
106};
107
109{
110 enum op_t
111 {
112 negate,
113 complement,
114 logical_not
115 } op;
116 expr_ptr rhs;
117
118 explicit ExprUnaryArithmetic(op_t op, expr_ptr e) : op(op), rhs(std::move(e))
119 {}
120
121 Any evaluate(Environment& env) const override
122 {
123 auto rhs_v = rhs->evaluate(env);
124 if(rhs_v.isNumber())
125 {
126 const double rv = rhs_v.cast<double>();
127 switch(op)
128 {
129 case negate:
130 return Any(-rv);
131 case complement:
132 // Use the checked conversion, like the binary bitwise operators do.
133 // A plain static_cast is undefined for NaN and for values at or
134 // beyond the int64_t range: the hand-written comparison used here
135 // before let NaN (every comparison is false) and 2^63 (not greater
136 // than double(INT64_MAX), which rounds up to 2^63) through, and
137 // silently truncated fractional values.
138 try
139 {
140 return Any(static_cast<double>(~rhs_v.cast<int64_t>()));
141 }
142 catch(const std::exception&)
143 {
144 throw RuntimeError("Number out of range for bitwise operation");
145 }
146 case logical_not:
147 return Any(static_cast<double>(!static_cast<bool>(rv)));
148 }
149 }
150 else if(rhs_v.isString())
151 {
152 if(op == logical_not)
153 {
154 const auto str = rhs_v.cast<std::string>();
155 if(str == "true" || str == "True" || str == "TRUE" || str == "1")
156 {
157 return Any(0.0);
158 }
159 if(str == "false" || str == "False" || str == "FALSE" || str == "0")
160 {
161 return Any(1.0);
162 }
163 }
164 throw RuntimeError("Invalid operator for std::string");
165 }
166 throw RuntimeError("ExprUnaryArithmetic: undefined");
167 }
168};
169
171{
172 enum op_t
173 {
174 plus,
175 minus,
176 times,
177 div,
178 concat,
179
180 bit_and,
181 bit_or,
182 bit_xor,
183
184 logic_and,
185 logic_or
186 } op;
187
188 const char* opStr() const
189 {
190 switch(op)
191 {
192 case plus:
193 return "+";
194 case minus:
195 return "-";
196 case times:
197 return "*";
198 case div:
199 return "/";
200 case concat:
201 return "..";
202 case bit_and:
203 return "&";
204 case bit_or:
205 return "|";
206 case bit_xor:
207 return "^";
208 case logic_and:
209 return "&&";
210 case logic_or:
211 return "||";
212 }
213 return "";
214 }
215
216 expr_ptr lhs, rhs;
217
218 explicit ExprBinaryArithmetic(expr_ptr lhs, op_t op, expr_ptr rhs)
219 : op(op), lhs(std::move(lhs)), rhs(std::move(rhs))
220 {}
221
222 Any evaluate(Environment& env) const override
223 {
224 auto lhs_v = lhs->evaluate(env);
225 auto rhs_v = rhs->evaluate(env);
226
227 if(lhs_v.empty())
228 {
229 throw RuntimeError(ErrorNotInit("left", opStr()));
230 }
231 if(rhs_v.empty())
232 {
233 throw RuntimeError(ErrorNotInit("right", opStr()));
234 }
235
236 if(rhs_v.isNumber() && lhs_v.isNumber())
237 {
238 auto lv = lhs_v.cast<double>();
239 auto rv = rhs_v.cast<double>();
240
241 switch(op)
242 {
243 case plus:
244 return Any(lv + rv);
245 case minus:
246 return Any(lv - rv);
247 case times:
248 return Any(lv * rv);
249 case div:
250 return Any(lv / rv);
251 default: {
252 }
253 }
254
255 if(op == bit_and || op == bit_or || op == bit_xor)
256 {
257 try
258 {
259 int64_t li = lhs_v.cast<int64_t>();
260 int64_t ri = rhs_v.cast<int64_t>();
261 switch(op)
262 {
263 case bit_and:
264 return Any(static_cast<double>(li & ri));
265 case bit_or:
266 return Any(static_cast<double>(li | ri));
267 case bit_xor:
268 return Any(static_cast<double>(li ^ ri));
269 default: {
270 }
271 }
272 }
273 catch(...)
274 {
275 throw RuntimeError("Binary operators are not allowed if "
276 "one of the operands is not an integer");
277 }
278 }
279
280 if(op == logic_or || op == logic_and)
281 {
282 try
283 {
284 auto lb = lhs_v.cast<bool>();
285 auto rb = rhs_v.cast<bool>();
286 switch(op)
287 {
288 case logic_or:
289 return Any(static_cast<double>(lb || rb));
290 case logic_and:
291 return Any(static_cast<double>(lb && rb));
292 default: {
293 }
294 }
295 }
296 catch(...)
297 {
298 throw RuntimeError("Logic operators are not allowed if "
299 "one of the operands is not castable to bool");
300 }
301 }
302 }
303 else if(rhs_v.isString() && lhs_v.isString() && op == plus)
304 {
305 return Any(lhs_v.cast<std::string>() + rhs_v.cast<std::string>());
306 }
307 else if(op == concat && ((rhs_v.isString() && lhs_v.isString()) ||
308 (rhs_v.isString() && lhs_v.isNumber()) ||
309 (rhs_v.isNumber() && lhs_v.isString())))
310 {
311 return Any(lhs_v.cast<std::string>() + rhs_v.cast<std::string>());
312 }
313 else
314 {
315 throw RuntimeError("Operation not permitted");
316 }
317
318 return {}; // unreachable
319 }
320};
321
322template <typename T>
323bool IsSame(const T& lv, const T& rv)
324{
325 if constexpr(std::is_same_v<double, T>)
326 {
327 constexpr double EPS = static_cast<double>(std::numeric_limits<float>::epsilon());
328 return std::abs(lv - rv) <= EPS;
329 }
330 else
331 {
332 return (lv == rv);
333 }
334}
335
337{
338 enum op_t
339 {
340 equal,
341 not_equal,
342 less,
343 greater,
344 less_equal,
345 greater_equal
346 };
347
348 const char* opStr(op_t op) const
349 {
350 switch(op)
351 {
352 case equal:
353 return "==";
354 case not_equal:
355 return "!=";
356 case less:
357 return "<";
358 case greater:
359 return ">";
360 case less_equal:
361 return "<=";
362 case greater_equal:
363 return ">=";
364 }
365 return "";
366 }
367
368 std::vector<op_t> ops;
369 std::vector<expr_ptr> operands;
370
371 Any evaluate(Environment& env) const override
372 {
373 auto SwitchImpl = [&](const auto& lv, const auto& rv, op_t op) {
374 switch(op)
375 {
376 case equal:
377 if(!IsSame(lv, rv))
378 return false;
379 break;
380 case not_equal:
381 if(IsSame(lv, rv))
382 return false;
383 break;
384 case less:
385 if(lv >= rv)
386 return false;
387 break;
388 case greater:
389 if(lv <= rv)
390 return false;
391 break;
392 case less_equal:
393 if(lv > rv)
394 return false;
395 break;
396 case greater_equal:
397 if(lv < rv)
398 return false;
399 break;
400 }
401 return true;
402 };
403
404 auto lhs_v = operands[0]->evaluate(env);
405 for(auto i = 0u; i != ops.size(); ++i)
406 {
407 auto rhs_v = operands[i + 1]->evaluate(env);
408
409 if(lhs_v.empty())
410 {
411 throw RuntimeError(ErrorNotInit("left", opStr(ops[i])));
412 }
413 if(rhs_v.empty())
414 {
415 throw RuntimeError(ErrorNotInit("right", opStr(ops[i])));
416 }
417 const Any False(0.0);
418
419 if(lhs_v.isNumber() && rhs_v.isNumber())
420 {
421 auto lv = lhs_v.cast<double>();
422 auto rv = rhs_v.cast<double>();
423 if(!SwitchImpl(lv, rv, ops[i]))
424 {
425 return False;
426 }
427 }
428 else if(lhs_v.isString() && rhs_v.isString())
429 {
430 auto lv = lhs_v.cast<SimpleString>();
431 auto rv = rhs_v.cast<SimpleString>();
432 if(!SwitchImpl(lv, rv, ops[i]))
433 {
434 return False;
435 }
436 }
437 else if(lhs_v.isString() && rhs_v.isNumber())
438 {
439 auto lv = StringToDouble(lhs_v, env);
440 auto rv = rhs_v.cast<double>();
441 if(!SwitchImpl(lv, rv, ops[i]))
442 {
443 return False;
444 }
445 }
446 else if(lhs_v.isNumber() && rhs_v.isString())
447 {
448 auto lv = lhs_v.cast<double>();
449 auto rv = StringToDouble(rhs_v, env);
450 if(!SwitchImpl(lv, rv, ops[i]))
451 {
452 return False;
453 }
454 }
455 else
456 {
457 throw RuntimeError(StrCat("Can't mix different types in Comparison. "
458 "Left operand [",
459 BT::demangle(lhs_v.type()), "] right operand [",
460 BT::demangle(rhs_v.type()), "]"));
461 }
462 lhs_v = rhs_v;
463 }
464 return Any(1.0);
465 }
466};
467
468struct ExprIf : ExprBase
469{
470 expr_ptr condition, then, else_;
471
472 explicit ExprIf(expr_ptr condition, expr_ptr then, expr_ptr else_)
473 : condition(std::move(condition)), then(std::move(then)), else_(std::move(else_))
474 {}
475
476 Any evaluate(Environment& env) const override
477 {
478 const auto& v = condition->evaluate(env);
479 bool valid = (v.isType<SimpleString>() && v.cast<SimpleString>().size() > 0) ||
480 (v.cast<double>() != 0.0);
481 if(valid)
482 {
483 return then->evaluate(env);
484 }
485 else
486 {
487 return else_->evaluate(env);
488 }
489 }
490};
491
493{
494 enum op_t
495 {
496 assign_create,
497 assign_existing,
498 assign_plus,
499 assign_minus,
500 assign_times,
501 assign_div
502 } op;
503
504 const char* opStr() const
505 {
506 switch(op)
507 {
508 case assign_create:
509 return ":=";
510 case assign_existing:
511 return "=";
512 case assign_plus:
513 return "+=";
514 case assign_minus:
515 return "-=";
516 case assign_times:
517 return "*=";
518 case assign_div:
519 return "/=";
520 }
521 return "";
522 }
523
524 expr_ptr lhs, rhs;
525
526 explicit ExprAssignment(expr_ptr _lhs, op_t op, expr_ptr _rhs)
527 : op(op), lhs(std::move(_lhs)), rhs(std::move(_rhs))
528 {}
529
530 Any evaluate(Environment& env) const override
531 {
532 auto varname = dynamic_cast<ExprName*>(lhs.get());
533 if(!varname)
534 {
535 throw RuntimeError("Assignment left operand not a blackboard entry");
536 }
537 const auto& key = varname->name;
538
539 auto entry = env.vars->getEntry(key);
540 if(!entry)
541 {
542 // variable doesn't exist, create it if using operator assign_create
543 if(op == assign_create)
544 {
545 env.vars->createEntry(key, PortInfo());
546 entry = env.vars->getEntry(key);
547 if(!entry)
548 {
549 throw LogicError("Bug: report");
550 }
551 }
552 else
553 {
554 // fail otherwise
555 auto msg = StrCat("The blackboard entry [", key,
556 "] doesn't exist, yet.\n"
557 "If you want to create a new one, "
558 "use the operator "
559 "[:=] instead of [=]");
560 throw RuntimeError(msg);
561 }
562 }
563 auto value = rhs->evaluate(env);
564
565 std::scoped_lock lock(entry->entry_mutex);
566 auto* dst_ptr = &entry->value;
567
568 auto errorPrefix = [dst_ptr, &key]() {
569 return StrCat("Error assigning a value to entry [", key, "] with type [",
570 BT::demangle(dst_ptr->type()), "]. ");
571 };
572
573 if(value.empty())
574 {
575 throw RuntimeError(ErrorNotInit("right", opStr()));
576 }
577
578 if(op == assign_create || op == assign_existing)
579 {
580 // the very fist assignment can come from any type.
581 // In the future, type check will be done by Any::copyInto
582 if(dst_ptr->empty() && entry->info.type() == typeid(AnyTypeAllowed))
583 {
584 *dst_ptr = value;
585 }
586 else if(value.isString() && !dst_ptr->isString())
587 {
588 // special case: string to other type.
589 // Check if we can use the StringConverter
590 auto const str = value.cast<std::string>();
591 const auto* entry_info = env.vars->entryInfo(key);
592
593 if(auto converter = entry_info->converter())
594 {
595 *dst_ptr = converter(str);
596 }
597 else if(dst_ptr->isNumber())
598 {
599 auto num_value = StringToDouble(value, env);
600 *dst_ptr = Any(num_value);
601 }
602 else
603 {
604 auto msg = StrCat(errorPrefix(),
605 "\nThe right operand is a string, "
606 "can't convert to ",
607 demangle(dst_ptr->type()));
608 throw RuntimeError(msg);
609 }
610 }
611 else
612 {
613 try
614 {
615 value.copyInto(*dst_ptr);
616 }
617 catch(std::exception&)
618 {
619 auto msg = StrCat(errorPrefix(), "\nThe right operand has type [",
620 BT::demangle(value.type()), "] and can't be converted to [",
621 BT::demangle(dst_ptr->type()), "]");
622 throw RuntimeError(msg);
623 }
624 }
625 entry->sequence_id++;
626 entry->stamp = std::chrono::steady_clock::now().time_since_epoch();
627 return *dst_ptr;
628 }
629
630 if(dst_ptr->empty())
631 {
632 throw RuntimeError(ErrorNotInit("left", opStr()));
633 }
634
635 // temporary use
636 Any temp_variable = *dst_ptr;
637
638 if(value.isNumber())
639 {
640 if(!temp_variable.isNumber())
641 {
642 throw RuntimeError("This Assignment operator can't be used "
643 "with a non-numeric type");
644 }
645
646 auto lv = temp_variable.cast<double>();
647 auto rv = value.cast<double>();
648 switch(op)
649 {
650 case assign_plus:
651 temp_variable = Any(lv + rv);
652 break;
653 case assign_minus:
654 temp_variable = Any(lv - rv);
655 break;
656 case assign_times:
657 temp_variable = Any(lv * rv);
658 break;
659 case assign_div:
660 temp_variable = Any(lv / rv);
661 break;
662 default: {
663 }
664 }
665 }
666 else if(value.isString())
667 {
668 if(op == assign_plus)
669 {
670 auto lv = temp_variable.cast<std::string>();
671 auto rv = value.cast<std::string>();
672 temp_variable = Any(lv + rv);
673 }
674 else
675 {
676 throw RuntimeError("Operator not supported for strings");
677 }
678 }
679
680 temp_variable.copyInto(*dst_ptr);
681 entry->sequence_id++;
682 entry->stamp = std::chrono::steady_clock::now().time_since_epoch();
683 return *dst_ptr;
684 }
685};
686} // namespace BT::Ast
687
688namespace BT::Scripting
689{
690
691/// Parse a script string into a list of AST expression nodes.
692/// Throws std::runtime_error on parse failure.
693std::vector<Ast::expr_ptr> parseStatements(const std::string& script);
694
695} // namespace BT::Scripting
Definition: safe_any.hpp:57
Definition: exceptions.h:61
Definition: simple_string.hpp:19
Definition: action_node.h:24
The Environment class is used to encapsulate the information and states needed by the scripting langu...
Definition: script_parser.hpp:32
Definition: operators.hpp:493
Definition: operators.hpp:54
Definition: operators.hpp:171
Definition: operators.hpp:337
Definition: operators.hpp:469
Definition: operators.hpp:68
Definition: operators.hpp:81
Definition: operators.hpp:109