Skip to content

Commit a552f62

Browse files
committed
Implemented parsing conditional expressions
1 parent bef8761 commit a552f62

File tree

3 files changed

+379
-11
lines changed

3 files changed

+379
-11
lines changed

include/ur_client_library/control/script_reader.h

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,16 @@ class ScriptReader
5050
RobotType robot_type;
5151
};
5252

53-
using DataDict = std::unordered_map<std::string, std::variant<std::string, double, int, bool>>;
53+
using DataVariant = std::variant<std::string, double, int, bool>;
54+
using DataDict = std::unordered_map<std::string, DataVariant>;
5455

5556
ScriptReader() = default;
5657

5758
std::string readScriptFile(const std::string& filename, const DataDict& data = DataDict());
5859

60+
static bool checkCondition(const std::string& condition, const DataDict& data);
61+
static bool parseBoolean(const std::string& str);
62+
5963
private:
6064
enum BlockType
6165
{
@@ -73,10 +77,27 @@ class ScriptReader
7377

7478
std::filesystem::path script_path_;
7579

76-
std::string readFileContent(const std::string& filename);
80+
static std::string readFileContent(const std::string& filename);
7781
void replaceIncludes(std::string& script_code);
78-
void replaceVariables(std::string& script_code, const DataDict& data);
79-
void replaceConditionals(std::string& script_code, const DataDict& data);
82+
static void replaceVariables(std::string& script_code, const DataDict& data);
83+
static void replaceConditionals(std::string& script_code, const DataDict& data);
8084
};
85+
86+
bool operator<(const ScriptReader::DataVariant& lhs, const ScriptReader::DataVariant& rhs);
87+
bool operator>(const ScriptReader::DataVariant& lhs, const ScriptReader::DataVariant& rhs);
88+
bool operator==(const ScriptReader::DataVariant& lhs, const ScriptReader::DataVariant& rhs);
89+
90+
inline bool operator!=(const ScriptReader::DataVariant& lhs, const ScriptReader::DataVariant& rhs)
91+
{
92+
return !(lhs == rhs);
93+
}
94+
inline bool operator<=(const ScriptReader::DataVariant& lhs, const ScriptReader::DataVariant& rhs)
95+
{
96+
return (lhs < rhs || lhs == rhs);
97+
}
98+
inline bool operator>=(const ScriptReader::DataVariant& lhs, const ScriptReader::DataVariant& rhs)
99+
{
100+
return (lhs > rhs || lhs == rhs);
101+
}
81102
} // namespace control
82103
} // namespace urcl

src/control/script_reader.cpp

Lines changed: 195 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,111 @@ namespace urcl
3939
{
4040
namespace control
4141
{
42+
43+
bool ScriptReader::parseBoolean(const std::string& str)
44+
{
45+
std::string lower = str;
46+
std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); });
47+
48+
if (lower == "true" || lower == "1" || lower == "yes" || lower == "on")
49+
{
50+
return true;
51+
}
52+
else if (lower == "false" || lower == "0" || lower == "no" || lower == "off")
53+
{
54+
return false;
55+
}
56+
else
57+
{
58+
std::stringstream ss;
59+
ss << "Invalid boolean value: '" << str << "'. Expected 'true', 'false', '1', '0', 'yes', 'no', 'on', or 'off'.";
60+
URCL_LOG_ERROR(ss.str().c_str());
61+
throw UrException(ss.str().c_str());
62+
}
63+
}
64+
65+
bool operator<(const ScriptReader::DataVariant& lhs, const ScriptReader::DataVariant& rhs)
66+
{
67+
if (std::holds_alternative<double>(lhs))
68+
{
69+
if (std::holds_alternative<double>(rhs))
70+
{
71+
return std::get<double>(lhs) < std::get<double>(rhs);
72+
}
73+
else if (std::holds_alternative<int>(rhs))
74+
{
75+
return std::get<double>(lhs) < static_cast<double>(std::get<int>(rhs));
76+
}
77+
}
78+
if (std::holds_alternative<int>(lhs))
79+
{
80+
if (std::holds_alternative<double>(rhs))
81+
{
82+
return static_cast<double>(std::get<int>(lhs)) < std::get<double>(rhs);
83+
}
84+
else if (std::holds_alternative<int>(rhs))
85+
{
86+
return std::get<int>(lhs) < std::get<int>(rhs);
87+
}
88+
}
89+
throw std::invalid_argument("The comparison operator is only allowed for numeric values.");
90+
}
91+
92+
bool operator>(const ScriptReader::DataVariant& lhs, const ScriptReader::DataVariant& rhs)
93+
{
94+
return !(lhs < rhs || lhs == rhs);
95+
}
96+
97+
bool operator==(const ScriptReader::DataVariant& lhs, const ScriptReader::DataVariant& rhs)
98+
{
99+
if (lhs.index() != rhs.index())
100+
{
101+
// Allow comparison between int and double
102+
if ((std::holds_alternative<int>(lhs) && std::holds_alternative<double>(rhs)))
103+
{
104+
return static_cast<double>(std::get<int>(lhs)) == std::get<double>(rhs);
105+
}
106+
if ((std::holds_alternative<double>(lhs) && std::holds_alternative<int>(rhs)))
107+
{
108+
return std::get<double>(lhs) == static_cast<double>(std::get<int>(rhs));
109+
}
110+
// Allow comparison between bool and int/double
111+
if (std::holds_alternative<bool>(lhs))
112+
{
113+
if (std::holds_alternative<double>(rhs))
114+
{
115+
return std::abs((static_cast<double>(std::get<bool>(lhs)) - std::get<double>(rhs))) < 0.0001;
116+
}
117+
if (std::holds_alternative<int>(rhs))
118+
{
119+
return std::abs((static_cast<double>(std::get<bool>(lhs)) - std::get<int>(rhs))) == 0;
120+
}
121+
}
122+
throw std::invalid_argument(
123+
"Checking equality of types is not allowed: " +
124+
std::string(lhs.index() == 0 ? "string" : (lhs.index() == 1 ? "double" : (lhs.index() == 2 ? "int" : "bool"))) +
125+
" with " +
126+
std::string(rhs.index() == 0 ? "string" : (rhs.index() == 1 ? "double" : (rhs.index() == 2 ? "int" : "bool"))));
127+
}
128+
if (std::holds_alternative<std::string>(lhs))
129+
{
130+
return std::get<std::string>(lhs) == std::get<std::string>(rhs);
131+
}
132+
if (std::holds_alternative<double>(lhs))
133+
{
134+
return std::get<double>(lhs) == std::get<double>(rhs);
135+
}
136+
if (std::holds_alternative<int>(lhs))
137+
{
138+
return std::get<int>(lhs) == std::get<int>(rhs);
139+
}
140+
if (std::holds_alternative<bool>(lhs))
141+
{
142+
return std::get<bool>(lhs) == std::get<bool>(rhs);
143+
}
144+
throw std::runtime_error("Unknown variant type passed to equality check. Please contact the developers.");
145+
}
146+
42147
std::string ScriptReader::readScriptFile(const std::string& filename, const DataDict& data)
43148
{
44149
script_path_ = filename;
@@ -149,8 +254,7 @@ void ScriptReader::replaceConditionals(std::string& script_code, const DataDict&
149254
if (std::regex_search(line, match, if_pattern))
150255
{
151256
std::string condition = match[1];
152-
bool result = data.count(condition) && std::holds_alternative<bool>(data.at(condition)) &&
153-
std::get<bool>(data.at(condition));
257+
bool result = checkCondition(condition, data);
154258
bool parent_render = block_stack.empty() ? true : block_stack.top().should_render;
155259
block_stack.push({ IF, result, parent_render && result, parent_render });
156260
}
@@ -194,8 +298,7 @@ void ScriptReader::replaceConditionals(std::string& script_code, const DataDict&
194298
}
195299
else
196300
{
197-
bool render = block_stack.empty() ? true : block_stack.top().should_render;
198-
if (render)
301+
if (block_stack.empty() ? true : block_stack.top().should_render)
199302
{
200303
output << line << "\n";
201304
}
@@ -204,6 +307,94 @@ void ScriptReader::replaceConditionals(std::string& script_code, const DataDict&
204307

205308
script_code = output.str();
206309
}
310+
bool ScriptReader::checkCondition(const std::string& condition, const DataDict& data)
311+
{
312+
const std::string trimmed = std::regex_replace(condition, std::regex("^\\s+|\\s+$"), "");
313+
const std::vector<std::string> valid_operators = { "==", "!=", "<", ">", "<=", ">=" };
314+
std::regex expression_pattern(R"(([a-zA-Z_][a-zA-Z0-9_]*)\s*([!=<>]=?)\s*(["']?[^'"]*["']?))");
315+
316+
std::smatch match;
317+
if (std::regex_search(trimmed, match, expression_pattern))
318+
{
319+
std::string variable_name = match[1];
320+
std::string comp_operator = match[2];
321+
std::string value_str = match[3];
322+
DataVariant value = value_str;
323+
324+
if (!data.count(variable_name))
325+
{
326+
throw UnknownVariable(variable_name);
327+
}
328+
329+
std::stringstream ss;
330+
ss << "Evaluating trimmed: " << variable_name << " " << comp_operator << " " << value_str << std::endl;
331+
URCL_LOG_DEBUG(ss.str().c_str());
332+
333+
// Is the value a string, a number or a variable?
334+
std::regex string_pattern(R"(^['"]([^'"]+)?['"]$)");
335+
std::regex number_pattern(R"(^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$)");
336+
std::regex boolean_pattern(R"(^(true|false|yes|no|on|off)$)", std::regex::icase);
337+
if (std::regex_search(value_str, match, string_pattern))
338+
{
339+
value = match[1]; // Extract the string content without quotes
340+
}
341+
else if (std::regex_search(value_str, match, number_pattern))
342+
{
343+
value = std::stod(value_str);
344+
}
345+
else if (std::regex_search(value_str, match, boolean_pattern))
346+
{
347+
value = parseBoolean(value_str);
348+
}
349+
else if (data.count(value_str))
350+
{
351+
value = data.at(value_str);
352+
}
353+
else
354+
{
355+
throw UnknownVariable(value_str);
356+
}
357+
358+
if (comp_operator == "==")
359+
{
360+
return data.at(variable_name) == value;
361+
}
362+
else if (comp_operator == "!=")
363+
{
364+
return data.at(variable_name) != value;
365+
}
366+
else if (comp_operator == "<")
367+
{
368+
return data.at(variable_name) < value;
369+
}
370+
else if (comp_operator == ">")
371+
{
372+
return data.at(variable_name) > value;
373+
}
374+
else if (comp_operator == "<=")
375+
{
376+
return data.at(variable_name) <= value;
377+
}
378+
else if (comp_operator == ">=")
379+
{
380+
return data.at(variable_name) >= value;
381+
}
382+
}
383+
else if (std::regex_match(trimmed, std::regex(R"([a-zA-Z_][a-zA-Z0-9_]*)")))
384+
{
385+
if (!data.count(trimmed))
386+
{
387+
throw UnknownVariable(trimmed);
388+
}
389+
if (std::holds_alternative<bool>(data.at(trimmed)))
390+
{
391+
return std::get<bool>(data.at(trimmed));
392+
}
393+
}
394+
395+
throw std::runtime_error("trimmed evaluation failed: `" + trimmed +
396+
"`. Expected a boolean value, but got a different type.");
397+
}
207398

208399
} // namespace control
209400
} // namespace urcl

0 commit comments

Comments
 (0)