From 31a42c27c84eabcdc0a8b7a646ee50c23af90350 Mon Sep 17 00:00:00 2001 From: Robert Plante Date: Wed, 11 Feb 2026 14:37:26 -0500 Subject: [PATCH] Removing need for RegEx when expanding ENV variables When starting up several programs with STATIC EDP on embedded hardware, this routine dominates the startup time due to the RegEx recompilation and `std::regex` internally being locale aware. Especially when the EDP XML is fairly large. This work replaces the RegEx with a small finite state machine that does a single linear pass over the input. Results from `perf` show this significantly reduces parsing time (as all text of interest is passed through this method). Signed-off-by: Robert Plante --- src/cpp/xmlparser/XMLElementParser.cpp | 107 ++++++++++++++----------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/src/cpp/xmlparser/XMLElementParser.cpp b/src/cpp/xmlparser/XMLElementParser.cpp index 98ca5c9d0fb..5e194f6db8f 100644 --- a/src/cpp/xmlparser/XMLElementParser.cpp +++ b/src/cpp/xmlparser/XMLElementParser.cpp @@ -48,65 +48,82 @@ static std::string process_environment( * Environment variable names used ... consist solely of uppercase letters, digits, and the '_' (underscore) * from the characters defined in Portable Character Set and do not begin with a digit. */ - std::regex expression("\\$\\{([A-Z_][A-Z0-9_]*)\\}"); - - // Algorithm inspired by https://stackoverflow.com/a/37516316/559350 + enum class ParseStage : uint8_t { + PLAIN_CHAR, + DOLLAR, + OPEN_CURLY, + ENV_CHAR, + }; // This will hold the accumulated substitution result std::string ret_val{}; - // Iterators to first and last character of the input string - auto first = input.cbegin(); - auto last = input.cend(); - - // Position of last match in the input string - std::smatch::difference_type last_match_position = 0; + bool all_expanded = true; + const char* cursor = input.c_str(); - // Iterator to the character after the last match in the input string - auto last_match_end = first; + ParseStage parse_stage = ParseStage::PLAIN_CHAR; + const char* match_start = cursor; + const char* last_match_end = cursor; + std::string var_name; - // Functor called to process each match - auto match_cb = [&](const std::smatch& match) - { - // Compute substitution value - std::string var_name = match[1]; - std::string value = ""; - if (var_name == "_") - { - // Silently ignore ${_} since it might expose sensitive information (full path to executable). - EPROSIMA_LOG_WARNING(XMLPARSER, "Ignoring environment variable ${_}"); + for (; *cursor != '\0'; ++cursor) { + switch (parse_stage) { + case ParseStage::PLAIN_CHAR: + if ('$' == *cursor) { + parse_stage = ParseStage::DOLLAR; } - else if (dds::RETCODE_OK != SystemInfo::get_env(var_name, value)) - { - EPROSIMA_LOG_ERROR(XMLPARSER, "Could not find a value for environment variable " << var_name); + break; + case ParseStage::DOLLAR: + if ('{' == *cursor) { + match_start = cursor + 1; + parse_stage = ParseStage::OPEN_CURLY; + } else { + parse_stage = ParseStage::PLAIN_CHAR; + } + break; + case ParseStage::OPEN_CURLY: + if ((0 != std::isupper(*cursor)) || ('_' == *cursor)) { + // Must start with A-Z or '_' + parse_stage = ParseStage::ENV_CHAR; + } else { + parse_stage = ParseStage::PLAIN_CHAR; } + break; + case ParseStage::ENV_CHAR: + if ('}' == *cursor) { + // Append everything after last '}' (or beginning of input) up until '$' + ret_val.append(last_match_end, match_start - 2); - // Compute number of non-matching characters between this match and the last one - auto this_match_position = match.position(); - auto diff = this_match_position - last_match_position; + // Extract the part between curly braces to simplify lookup + var_name.assign(match_start, cursor); - // Append non-matching characters to return value - auto this_match_start = last_match_end; - std::advance(this_match_start, diff); - ret_val.append(last_match_end, this_match_start); + std::string value = ""; - // Append substitution value to return value - ret_val.append(value); + if (var_name == "_") + { + // Silently ignore ${_} since it might expose sensitive information (full path to executable). + EPROSIMA_LOG_WARNING(XMLPARSER, "Ignoring environment variable ${_}"); + } + else if (dds::RETCODE_OK != SystemInfo::get_env(var_name, value)) + { + EPROSIMA_LOG_ERROR(XMLPARSER, "Could not find a value for environment variable " << var_name); + } - // Prepare for next iteration - auto match_length = match.length(); - last_match_position = this_match_position + match_length; - last_match_end = this_match_start; - std::advance(last_match_end, match_length); - }; + ret_val.append(value); - // Substitute all matches - std::sregex_iterator begin(first, last, expression); - std::sregex_iterator end; - std::for_each(begin, end, match_cb); + last_match_end = cursor + 1; + parse_stage = ParseStage::PLAIN_CHAR; + } else if ((0 == std::isupper(*cursor)) && (0 == std::isdigit(*cursor)) && + ('_' != *cursor)) { + // Can only be 0-9, A-Z, or '_' + parse_stage = ParseStage::PLAIN_CHAR; + } + break; + } + } - // Append characters after last match - ret_val.append(last_match_end, last); + // Append any remaining characters (cursor at '\0') + ret_val.append(last_match_end, cursor); return ret_val; }