script_parser_test.cpp
Go to the documentation of this file.
1 #include <gtest/gtest.h>
2 
5 #include "../sample_nodes/dummy_nodes.h"
6 #include "test_helper.hpp"
7 
9 
11 {
12  auto input = lexy::zstring_input<lexy::utf8_encoding>(text);
13  auto result = lexy::parse<BT::Grammar::stmt>(input, lexy_ext::report_error);
14 
15  if(result.has_value())
16  {
17  auto exprs = LEXY_MOV(result).value();
18  for(auto i = 0u; i < exprs.size() - 1; ++i)
19  {
20  exprs[i]->evaluate(environment);
21  }
22  return exprs.back()->evaluate(environment);
23  }
24  else
25  {
26  return {};
27  }
28 }
29 
30 TEST(ParserTest, AnyTypes)
31 {
32  auto Parse = [](const char* str) {
33  return lexy::parse<BT::Grammar::AnyValue>(lexy::zstring_input(str),
35  };
36 
37  auto result = Parse("628");
38  EXPECT_TRUE(result.is_success());
39  EXPECT_EQ(result.value().cast<int>(), 628);
40 
41  result = Parse("-628");
42  EXPECT_TRUE(result.is_success());
43  EXPECT_EQ(result.value().cast<int>(), -628);
44 
45  result = Parse("0x100");
46  EXPECT_TRUE(result.is_success());
47  EXPECT_EQ(result.value().cast<int>(), 256);
48 
49  result = Parse("0X100");
50  EXPECT_TRUE(result.is_success());
51  EXPECT_EQ(result.value().cast<int>(), 256);
52 
53  result = Parse("3.14");
54  EXPECT_TRUE(result.is_success());
55  EXPECT_EQ(result.value().cast<double>(), 3.14);
56 
57  result = Parse("-3.14");
58  EXPECT_TRUE(result.is_success());
59  EXPECT_EQ(result.value().cast<double>(), -3.14);
60 
61  result = Parse("3.14e2");
62  EXPECT_TRUE(result.is_success());
63  EXPECT_EQ(result.value().cast<double>(), 314);
64 
65  result = Parse("3.14e-2");
66  EXPECT_TRUE(result.is_success());
67  EXPECT_EQ(result.value().cast<double>(), 0.0314);
68 
69  result = Parse("3e2");
70  EXPECT_TRUE(result.is_success());
71  EXPECT_EQ(result.value().cast<double>(), 300);
72 
73  result = Parse("3e-2");
74  EXPECT_TRUE(result.is_success());
75  EXPECT_EQ(result.value().cast<double>(), 0.03);
76 
77  result = Parse("'hello world '");
78  EXPECT_TRUE(result.is_success());
79  EXPECT_EQ(result.value().cast<std::string>(), "hello world ");
80 
81  result = Parse("true");
82  EXPECT_TRUE(result.is_success());
83  EXPECT_EQ(result.value().cast<int>(), 1);
84 
85  result = Parse("false");
86  EXPECT_TRUE(result.is_success());
87  EXPECT_EQ(result.value().cast<int>(), 0);
88 }
89 
90 TEST(ParserTest, AnyTypes_Failing)
91 {
92  std::istringstream null_sink;
93 
94  auto Parse = [](const char* str) {
95  return lexy::parse<BT::Grammar::AnyValue>(lexy::zstring_input(str),
97  };
98 
99  EXPECT_TRUE(!Parse("0X100g").is_success());
100 
101  EXPECT_TRUE(!Parse("0X100.").is_success());
102 
103  EXPECT_TRUE(!Parse("3foo").is_success());
104 
105  EXPECT_TRUE(!Parse("65.").is_success());
106 
107  EXPECT_TRUE(!Parse("65.43foo").is_success());
108 
109  EXPECT_TRUE(!Parse("foo").is_success());
110 }
111 
112 TEST(ParserTest, Equations)
113 {
115 
116  auto GetResult = [&environment](const char* text) -> BT::Any {
117  return GetScriptResult(environment, text);
118  };
119  //-------------------
120  const auto& variables = environment.vars;
121  EXPECT_EQ(GetResult("x:= 3; y:=5; x+y").cast<double>(), 8.0);
122  EXPECT_EQ(variables->getKeys().size(), 2);
123  EXPECT_EQ(variables->get<double>("x"), 3.0);
124  EXPECT_EQ(variables->get<double>("y"), 5.0);
125 
126  EXPECT_EQ(GetResult("x+=1").cast<double>(), 4.0);
127  EXPECT_EQ(variables->get<double>("x"), 4.0);
128 
129  EXPECT_EQ(GetResult("x += 1").cast<double>(), 5.0);
130  EXPECT_EQ(variables->get<double>("x"), 5.0);
131 
132  EXPECT_EQ(GetResult("x-=1").cast<double>(), 4.0);
133  EXPECT_EQ(variables->get<double>("x"), 4.0);
134 
135  EXPECT_EQ(GetResult("x -= 1").cast<double>(), 3.0);
136  EXPECT_EQ(variables->get<double>("x"), 3.0);
137 
138  EXPECT_EQ(GetResult("x*=2").cast<double>(), 6.0);
139  EXPECT_EQ(variables->get<double>("x"), 6.0);
140 
141  EXPECT_EQ(GetResult("-x").cast<double>(), -6.0);
142 
143  EXPECT_EQ(GetResult("x/=2").cast<double>(), 3.0);
144  EXPECT_EQ(variables->get<double>("x"), 3.0);
145 
146  EXPECT_EQ(GetResult("y").cast<double>(), 5.0);
147  EXPECT_EQ(GetResult("y/2").cast<double>(), 2.5);
148  EXPECT_EQ(GetResult("y*2").cast<double>(), 10.0);
149  EXPECT_EQ(GetResult("y-x").cast<double>(), 2.0);
150 
151  EXPECT_EQ(GetResult("y & x").cast<double>(), (5 & 3));
152  EXPECT_EQ(GetResult("y | x").cast<double>(), (5 | 3));
153  EXPECT_EQ(GetResult("y ^ x").cast<double>(), (5 ^ 3));
154 
155  EXPECT_ANY_THROW(auto res = GetResult("y ^ 5.1").cast<double>());
156 
157  // test string variables
158  EXPECT_EQ(GetResult("A:='hello'; B:=' '; C:='world'; A+B+C").cast<std::string>(), "hell"
159  "o "
160  "worl"
161  "d");
162  EXPECT_EQ(variables->getKeys().size(), 5);
163  EXPECT_EQ(variables->get<std::string>("A"), "hello");
164  EXPECT_EQ(variables->get<std::string>("B"), " ");
165  EXPECT_EQ(variables->get<std::string>("C"), "world");
166 
167  // check that whitespaces are handled correctly
168  EXPECT_TRUE(!GetResult("A= ' right'; "
169  "B= ' center '; "
170  "C= 'left ' ")
171  .empty());
172 
173  EXPECT_EQ(variables->getKeys().size(), 5);
174  EXPECT_EQ(variables->get<std::string>("A"), " right");
175  EXPECT_EQ(variables->get<std::string>("B"), " center ");
176  EXPECT_EQ(variables->get<std::string>("C"), "left ");
177 
178  // can't change the type, once created
179  EXPECT_ANY_THROW(GetResult("x=A"));
180  EXPECT_ANY_THROW(GetResult("x='msg'"));
181  EXPECT_ANY_THROW(GetResult("A=1.0"));
182 
183  // Invalid assignments
184  EXPECT_ANY_THROW(GetResult(" 'hello' = 'world' "));
185  EXPECT_ANY_THROW(GetResult(" 'hello' = 2.0 "));
186  EXPECT_ANY_THROW(GetResult(" 3.0 = 2.0 "));
187 
188  size_t prev_size = variables->getKeys().size();
189  EXPECT_ANY_THROW(GetResult("new_var=69"));
190  EXPECT_EQ(variables->getKeys().size(), prev_size); // shouldn't increase
191 
192  // check comparisons
193  EXPECT_EQ(GetResult("x < y").cast<int>(), 1);
194  EXPECT_EQ(GetResult("y > x").cast<int>(), 1);
195  EXPECT_EQ(GetResult("y != x").cast<int>(), 1);
196  EXPECT_EQ(GetResult("y == x").cast<int>(), 0);
197 
198  EXPECT_EQ(GetResult(" 'hello' == 'hello'").cast<int>(), 1);
199  EXPECT_EQ(GetResult(" 'hello' != 'world'").cast<int>(), 1);
200  EXPECT_EQ(GetResult(" 'hello' < 'world'").cast<int>(), 1);
201  EXPECT_EQ(GetResult(" 'hello' > 'world'").cast<int>(), 0);
202 
203  EXPECT_NE(GetResult("x > y").cast<int>(), 1);
204  EXPECT_NE(GetResult("y < x").cast<int>(), 1);
205  EXPECT_NE(GetResult("y == x").cast<int>(), 1);
206 
207  EXPECT_EQ(GetResult("y == x ? 'T' : 'F'").cast<std::string>(), "F");
208  EXPECT_EQ(GetResult("y != x ? 'T' : 'F'").cast<std::string>(), "T");
209 
210  EXPECT_EQ(GetResult("y == x").cast<int>(), 0);
211  EXPECT_EQ(GetResult("y == 5").cast<int>(), 1);
212  EXPECT_EQ(GetResult("x == 3").cast<int>(), 1);
213 
214  EXPECT_EQ(GetResult(" true ").cast<int>(), 1);
215  EXPECT_EQ(GetResult(" 'true' ").cast<std::string>(), "true");
216 
217  GetResult("v1:= true; v2:= false");
218  EXPECT_EQ(variables->get<int>("v1"), 1);
219  EXPECT_EQ(variables->get<int>("v2"), 0);
220 
221  EXPECT_EQ(GetResult(" v2 = true ").cast<int>(), 1);
222  EXPECT_EQ(GetResult(" v2 = !false ").cast<int>(), 1);
223  EXPECT_EQ(GetResult(" v2 = !v2 ").cast<int>(), 0);
224 
225  EXPECT_EQ(GetResult("v1 && v2").cast<int>(), 0);
226  EXPECT_EQ(GetResult("v1 || v2").cast<int>(), 1);
227 
228  EXPECT_EQ(GetResult("(y == x) && (x == 3)").cast<int>(), 0);
229  EXPECT_EQ(GetResult("(y == x) || (x == 3)").cast<int>(), 1);
230 
231  EXPECT_EQ(GetResult(" y == x && x == 3 ").cast<int>(), 0);
232  EXPECT_EQ(GetResult(" y == x || x == 3 ").cast<int>(), 1);
233 
234  // we expect string to be casted to number
235  EXPECT_EQ(GetResult(" par1:='3'; par2:=3; par1==par2").cast<int>(), 1);
236  EXPECT_EQ(GetResult(" par1:='3'; par2:=4; par1!=par2").cast<int>(), 1);
237 }
238 
239 TEST(ParserTest, NotInitializedComparison)
240 {
242 
243  auto GetResult = [&environment](const char* text) -> BT::Any {
244  return GetScriptResult(environment, text);
245  };
246 
247  auto port_info = BT::PortInfo(BT::PortDirection::INOUT, typeid(uint8_t), {});
248  environment.vars->createEntry("x", port_info);
249 
250  EXPECT_ANY_THROW(GetResult("x < 0"));
251  EXPECT_ANY_THROW(GetResult("x == 0"));
252  EXPECT_ANY_THROW(GetResult("x > 0"));
253 
254  EXPECT_ANY_THROW(GetResult("x + 1"));
255  EXPECT_ANY_THROW(GetResult("x += 1"));
256 }
257 
258 TEST(ParserTest, EnumsBasic)
259 {
261 
262  auto GetResult = [&environment](const char* text) -> BT::Any {
263  return GetScriptResult(environment, text);
264  };
265 
266  enum Color
267  {
268  RED = 1,
269  BLUE = 3,
270  GREEN = 5
271  };
272 
273  environment.enums = std::make_shared<BT::EnumsTable>();
274  environment.enums->insert({ "RED", RED });
275  environment.enums->insert({ "BLUE", BLUE });
276  environment.enums->insert({ "GREEN", GREEN });
277  GetResult("A:=RED");
278  GetResult("B:=RED");
279  GetResult("C:=BLUE");
280 
281  EXPECT_EQ(GetResult("A==B").cast<int>(), 1);
282  EXPECT_EQ(GetResult("A!=C").cast<int>(), 1);
283 
284  EXPECT_EQ(GetResult("A").cast<Color>(), RED);
285  EXPECT_EQ(GetResult("B").cast<Color>(), RED);
286  EXPECT_EQ(GetResult("C").cast<Color>(), BLUE);
287 }
288 
289 TEST(ParserTest, EnumsXML)
290 {
291  BT::BehaviorTreeFactory factory;
292 
293  const std::string xml_text = R"(
294 
295  <root BTCPP_format="4" >
296  <BehaviorTree ID="MainTree">
297  <Script code = "A:=THE_ANSWER; color1:=RED; color2:=BLUE; color3:=GREEN" />
298  </BehaviorTree>
299  </root>)";
300 
301  enum Color
302  {
303  RED = 1,
304  BLUE = 3,
305  GREEN = 5
306  };
307 
308  factory.registerScriptingEnum("THE_ANSWER", 42);
309  factory.registerScriptingEnums<Color>();
310 
311  auto tree = factory.createTreeFromText(xml_text);
312  const auto status = tree.tickWhileRunning();
313  ASSERT_EQ(status, BT::NodeStatus::SUCCESS);
314 
315  const auto& blackboard = tree.subtrees.front()->blackboard;
316  ASSERT_EQ(blackboard->get<int>("A"), 42);
317  ASSERT_EQ(blackboard->get<int>("color1"), RED);
318  ASSERT_EQ(blackboard->get<int>("color2"), BLUE);
319  ASSERT_EQ(blackboard->get<int>("color3"), GREEN);
320 }
321 
323 {
324  BATT = 1,
326 };
327 
329 {
330  double percent = self.getInput<double>("percentage").value();
331  DeviceType devType;
332  auto res = self.getInput("deviceType", devType);
333  if(!res)
334  {
335  throw std::runtime_error(res.error());
336  }
337 
338  if(devType == DeviceType::BATT)
339  {
340  self.setOutput("isLowBattery", (percent < 25));
341  }
342  std::cout << "Device: " << devType << " Level: " << percent << std::endl;
344 }
345 
346 TEST(ParserTest, Enums_Issue_523)
347 {
348  BT::BehaviorTreeFactory factory;
349 
350  const std::string xml_text = R"(
351  <root BTCPP_format="4" >
352  <BehaviorTree ID="PowerManagerT">
353  <ReactiveSequence>
354  <Script code=" deviceA:=BATT; deviceB:=CONTROLLER; battery_level:=30 "/>
355  <CheckLevel deviceType="{deviceA}" percentage="{battery_level}" isLowBattery="{isLowBattery}"/>
356  <SaySomething message="FIRST low batteries!" _skipIf="!isLowBattery" />
357 
358  <Script code=" battery_level:=20 "/>
359  <CheckLevel deviceType="{deviceA}" percentage="{battery_level}" isLowBattery="{isLowBattery}"/>
360  <SaySomething message="SECOND low batteries!" _skipIf="!isLowBattery" />
361  </ReactiveSequence>
362  </BehaviorTree>
363  </root> )";
364 
365  factory.registerNodeType<DummyNodes::SaySomething>("SaySomething");
366  factory.registerSimpleCondition(
368  { BT::InputPort("percentage"), BT::InputPort("deviceType"),
369  BT::OutputPort("isLowBattery") });
370 
372 
373  auto tree = factory.createTreeFromText(xml_text);
374  const auto status = tree.tickWhileRunning();
375  ASSERT_EQ(status, BT::NodeStatus::SUCCESS);
376 
377  const auto& blackboard = tree.subtrees.front()->blackboard;
378  ASSERT_EQ(blackboard->get<int>("deviceA"), BATT);
379  ASSERT_EQ(blackboard->get<int>("deviceB"), CONTROLLER);
380  ASSERT_EQ(blackboard->get<bool>("isLowBattery"), true);
381 }
382 
384 {
385 public:
386  SampleNode595(const std::string& name, const BT::NodeConfiguration& config)
388  {}
389 
390  BT::NodeStatus tick() override
391  {
392  setOutput("find_enemy", 0);
394  }
396  {
397  return { BT::OutputPort<uint8_t>("find_enemy") };
398  }
399 };
400 
401 TEST(ParserTest, Issue595)
402 {
403  BT::BehaviorTreeFactory factory;
404 
405  const std::string xml_text = R"(
406  <root BTCPP_format="4" >
407  <BehaviorTree ID="PowerManagerT">
408  <Sequence>
409  <SampleNode595 find_enemy="{find_enemy}" />
410  <TestA _skipIf="find_enemy==0"/>
411  </Sequence>
412  </BehaviorTree>
413  </root> )";
414 
415  std::array<int, 1> counters;
416  RegisterTestTick(factory, "Test", counters);
417  factory.registerNodeType<SampleNode595>("SampleNode595");
418 
419  auto tree = factory.createTreeFromText(xml_text);
420  const auto status = tree.tickWhileRunning();
421 
422  ASSERT_EQ(status, BT::NodeStatus::SUCCESS);
423  ASSERT_EQ(0, counters[0]);
424 }
425 
426 TEST(ParserTest, NewLine)
427 {
428  BT::BehaviorTreeFactory factory;
429 
430  const std::string xml_text = R"(
431  <root BTCPP_format="4" >
432  <BehaviorTree ID="Main">
433  <Script code="A:=5;&#10;B:=6"/>
434  </BehaviorTree>
435  </root> )";
436 
437  auto tree = factory.createTreeFromText(xml_text);
438  const auto status = tree.tickWhileRunning();
439 
440  ASSERT_EQ(status, BT::NodeStatus::SUCCESS);
441  ASSERT_EQ(tree.rootBlackboard()->get<int>("A"), 5);
442  ASSERT_EQ(tree.rootBlackboard()->get<int>("B"), 6);
443 }
BT::PortInfo
Definition: basic_types.h:392
BT
Definition: ex01_wrap_legacy.cpp:29
LEXY_MOV
#define LEXY_MOV(...)
Definition: config.hpp:21
lexy::placeholders::_1
constexpr auto _1
Definition: bind.hpp:234
SampleNode595
Definition: script_parser_test.cpp:383
BT::PortDirection::INOUT
@ INOUT
BT::Any
Definition: safe_any.hpp:36
BT::TreeNode::config
const NodeConfig & config() const
Definition: tree_node.cpp:345
SampleNode595::tick
BT::NodeStatus tick() override
Method to be implemented by the user.
Definition: script_parser_test.cpp:390
BT::InputPort
std::pair< std::string, PortInfo > InputPort(StringView name, StringView description={})
Definition: basic_types.h:470
RegisterTestTick
void RegisterTestTick(BT::BehaviorTreeFactory &factory, const std::string &name_prefix, std::array< int, N > &tick_counters)
Definition: test_helper.hpp:15
BT::TreeNode
Abstract base class for Behavior Tree Nodes.
Definition: tree_node.h:118
cx::empty
constexpr auto empty(const C &c) -> decltype(c.empty())
Definition: wildcards.hpp:646
SampleNode595::SampleNode595
SampleNode595(const std::string &name, const BT::NodeConfiguration &config)
Definition: script_parser_test.cpp:386
DeviceType
DeviceType
Definition: script_parser_test.cpp:322
bt_factory.h
magic_enum::detail::value
constexpr E value(std::size_t i) noexcept
Definition: magic_enum.hpp:664
BT::BehaviorTreeFactory::registerScriptingEnum
void registerScriptingEnum(StringView name, int value)
Add an Enum to the scripting language. For instance if you do:
Definition: bt_factory.cpp:451
SampleNode595::providedPorts
static BT::PortsList providedPorts()
Definition: script_parser_test.cpp:395
test_helper.hpp
BT::Tree::tickWhileRunning
NodeStatus tickWhileRunning(std::chrono::milliseconds sleep_time=std::chrono::milliseconds(10))
Definition: bt_factory.cpp:609
BT::PortsList
std::unordered_map< std::string, PortInfo > PortsList
Definition: basic_types.h:585
lexy::bind
constexpr auto bind(Callback &&callback, BoundArgs &&... args)
Binds the operator() of the callback with pre-defined/remapped values.
Definition: bind.hpp:321
BT::BehaviorTreeFactory::registerNodeType
void registerNodeType(const std::string &ID, const PortsList &ports, ExtraArgs... args)
Definition: bt_factory.h:322
BT::BehaviorTreeFactory::createTreeFromText
Tree createTreeFromText(const std::string &text, Blackboard::Ptr blackboard=Blackboard::create())
createTreeFromText will parse the XML directly from string. The XML needs to contain either a single ...
Definition: bt_factory.cpp:395
Color
Color
Definition: gtest_enums.cpp:6
BT::SyncActionNode::SyncActionNode
SyncActionNode(const std::string &name, const NodeConfig &config)
Definition: action_node.cpp:52
BATT
@ BATT
Definition: script_parser_test.cpp:324
CONTROLLER
@ CONTROLLER
Definition: script_parser_test.cpp:325
DummyNodes::SaySomething
Definition: dummy_nodes.h:47
TEST
TEST(ParserTest, AnyTypes)
Definition: script_parser_test.cpp:30
BT::BehaviorTreeFactory
The BehaviorTreeFactory is used to create instances of a TreeNode at run-time.
Definition: bt_factory.h:205
GetScriptResult
BT::Any GetScriptResult(BT::Ast::Environment &environment, const char *text)
Definition: script_parser_test.cpp:10
BT::TreeNode::setOutput
Result setOutput(const std::string &key, const T &value)
setOutput modifies the content of an Output port
Definition: tree_node.h:558
BT::Blackboard::create
static Blackboard::Ptr create(Blackboard::Ptr parent={})
Definition: blackboard.h:63
environment
Environment * environment
BT::TreeNode::name
const std::string & name() const
Name of the instance, not the type.
Definition: tree_node.cpp:296
BT::NodeStatus::SUCCESS
@ SUCCESS
lexy_ext::report_error
constexpr auto report_error
An error callback that uses diagnostic_writer to print to stderr (by default).
Definition: report_error.hpp:381
BT::OutputPort
std::pair< std::string, PortInfo > OutputPort(StringView name, StringView description={})
Definition: basic_types.h:482
string_input.hpp
checkLevel
BT::NodeStatus checkLevel(BT::TreeNode &self)
Definition: script_parser_test.cpp:328
operators.hpp
BT::NodeConfig
Definition: tree_node.h:73
BT::BehaviorTreeFactory::registerSimpleCondition
void registerSimpleCondition(const std::string &ID, const SimpleConditionNode::TickFunctor &tick_functor, PortsList ports={})
registerSimpleCondition help you register nodes of type SimpleConditionNode.
Definition: bt_factory.cpp:153
xml_text
static const char * xml_text
Definition: ex01_wrap_legacy.cpp:52
BT::BehaviorTreeFactory::registerScriptingEnums
void registerScriptingEnums()
registerScriptingEnums is syntactic sugar to automatically register multiple enums....
Definition: bt_factory.h:458
BT::SyncActionNode
The SyncActionNode is an ActionNode that explicitly prevents the status RUNNING and doesn't require a...
Definition: action_node.h:52
BT::NodeStatus
NodeStatus
Definition: basic_types.h:33
lexy::zstring_input
constexpr string_input< Encoding > zstring_input(const CharT *str) noexcept
Definition: string_input.hpp:87
BT::Ast::Environment
The Environment class is used to encapsulate the information and states needed by the scripting langu...
Definition: script_parser.hpp:31


behaviortree_cpp_v4
Author(s): Davide Faconti
autogenerated on Fri Jun 28 2024 02:20:08