@@ -39,6 +39,111 @@ namespace urcl
3939{
4040namespace 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+
42147std::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