Skip to content

Commit fbcdf41

Browse files
Merge pull request #1121 from PickNikRobotics/fix-subtree-literal-numeric-types
Preserve numeric types for literal subtree port values
2 parents 3ff6a32 + 5e5f65b commit fbcdf41

File tree

2 files changed

+91
-2
lines changed

2 files changed

+91
-2
lines changed

src/xml_parsing.cpp

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212

1313
#include "behaviortree_cpp/basic_types.h"
1414

15+
#include <charconv>
1516
#include <cstdio>
1617
#include <cstring>
1718
#include <functional>
1819
#include <iostream>
20+
#include <limits>
1921
#include <list>
2022
#include <sstream>
2123
#include <string>
@@ -1149,10 +1151,59 @@ void BT::XMLParser::PImpl::recursivelyCreateSubtree(
11491151
}
11501152
else
11511153
{
1152-
// constant string: just set that constant value into the BB
1154+
// constant value: set it into the BB with appropriate type
11531155
// IMPORTANT: this must not be autoremapped!!!
11541156
new_bb->enableAutoRemapping(false);
1155-
new_bb->set(attr_name, static_cast<std::string>(attr_value));
1157+
const std::string str_value(attr_value);
1158+
1159+
// Try to preserve numeric types so that Script expressions
1160+
// can perform arithmetic without type-mismatch errors.
1161+
// Use std::from_chars with strict full-string validation to avoid
1162+
// false positives on compound strings like "1;2;3" or "2.2;2.4".
1163+
bool stored = false;
1164+
if(!str_value.empty())
1165+
{
1166+
const char* begin = str_value.data();
1167+
const char* end = begin + str_value.size();
1168+
// Try integer first (no decimal point, no exponent notation).
1169+
// Use int when the value fits, to match the most common port
1170+
// declarations. Fall back to int64_t for larger values.
1171+
if(str_value.find('.') == std::string::npos &&
1172+
str_value.find('e') == std::string::npos &&
1173+
str_value.find('E') == std::string::npos)
1174+
{
1175+
int64_t int_val = 0;
1176+
auto [ptr, ec] = std::from_chars(begin, end, int_val);
1177+
if(ec == std::errc() && ptr == end)
1178+
{
1179+
if(int_val >= std::numeric_limits<int>::min() &&
1180+
int_val <= std::numeric_limits<int>::max())
1181+
{
1182+
new_bb->set(attr_name, static_cast<int>(int_val));
1183+
}
1184+
else
1185+
{
1186+
new_bb->set(attr_name, int_val);
1187+
}
1188+
stored = true;
1189+
}
1190+
}
1191+
// Try double
1192+
if(!stored)
1193+
{
1194+
double dbl_val = 0;
1195+
auto [ptr, ec] = std::from_chars(begin, end, dbl_val);
1196+
if(ec == std::errc() && ptr == end)
1197+
{
1198+
new_bb->set(attr_name, dbl_val);
1199+
stored = true;
1200+
}
1201+
}
1202+
}
1203+
if(!stored)
1204+
{
1205+
new_bb->set(attr_name, str_value);
1206+
}
11561207
new_bb->enableAutoRemapping(do_autoremap);
11571208
}
11581209
}

tests/gtest_subtree.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,3 +974,41 @@ TEST(SubTree, NestedDuplicateNames_ShouldFail)
974974
// Should throw RuntimeError because of duplicate SubTree names
975975
ASSERT_THROW((void)factory.createTreeFromText(xml_text), RuntimeError);
976976
}
977+
978+
// Regression test: literal numeric values passed to subtrees should preserve
979+
// their numeric type so that Script expressions can do arithmetic.
980+
TEST(SubTree, LiteralNumericPortsPreserveType)
981+
{
982+
// clang-format off
983+
static const char* xml_text = R"(
984+
<root BTCPP_format="4" main_tree_to_execute="MainTree">
985+
986+
<BehaviorTree ID="MainTree">
987+
<Sequence>
988+
<SubTree ID="DoMath" int_val="42" dbl_val="3.14" str_val="hello"
989+
remapped_val="{from_parent}" />
990+
</Sequence>
991+
</BehaviorTree>
992+
993+
<BehaviorTree ID="DoMath">
994+
<Sequence>
995+
<ScriptCondition code=" int_val + 1 == 43 " />
996+
<ScriptCondition code=" dbl_val > 3.0 " />
997+
<ScriptCondition code=" remapped_val + 1 == 101 " />
998+
</Sequence>
999+
</BehaviorTree>
1000+
1001+
</root>
1002+
)";
1003+
// clang-format on
1004+
1005+
BehaviorTreeFactory factory;
1006+
1007+
auto tree = factory.createTreeFromText(xml_text);
1008+
1009+
// Set the remapped parent value as an integer
1010+
tree.rootBlackboard()->set("from_parent", 100);
1011+
1012+
const auto status = tree.tickWhileRunning();
1013+
ASSERT_EQ(status, NodeStatus::SUCCESS);
1014+
}

0 commit comments

Comments
 (0)