Component of the Week #8: bdlpcre_regex

Summary:
  • Provide a perl-compatible regular expression interface.

Regular expressions are a powerful tool for searching and manipulating text. The bdlpcre_regex component provides a C++ interface for Perl compatible regular expressions, which wraps PCRE2 (a popular C library).

Overview

The bdlpcre_regex component provides a powerful mechanism for compiling and matching regular expressions. It is built on top of the PCRE2 library, offering functionality similar to Perl 5.10 regular expressions. This component is particularly useful for tasks such as pattern matching, text extraction, and string replacement in a variety of applications.

In comparison to the standard library regular expression library, std::regex, bdlpcre::Regex supports C++03 (std::regex requires C++11), and benchmarks suggest bdlpcre::Regex performs better in many contexts. See the online comparison with the underlying PCRE2 library, a simple benchmark demonstration in compiler explorer.

The bdlpcre::RegEx class allows you to:

  • Compile a regular expression pattern using the prepare method.

  • Match strings against the compiled pattern using the match or matchRaw methods.

  • Replace matched substrings with new content using the replace or replaceRaw methods.

  • Extract matched substrings or capture groups using the match method.

The component supports advanced features such as:

  • Case-insensitive matching.

  • Multi-line matching.

  • UTF-8 support.

  • Just-in-time (JIT) compilation for performance optimization.

Matching a Simple Pattern

The following example demonstrates how to match a simple regular expression pattern using the bdlpcre::RegEx class:

#include <bdlpcre_regex.h>
#include <bsl_iostream.h>

using namespace BloombergLP;

int main() {
    bdlpcre::RegEx regEx;
    bsl::string errorMessage;
    size_t errorOffset;

    // Prepare the regular expression pattern
    const char PATTERN[] = "world";
    int rc = regEx.prepare(&errorMessage, &errorOffset, PATTERN);
    if (rc != 0) {
        bsl::cerr << "Error preparing regex: " << errorMessage << bsl::endl;
        return rc;
    }

    // Match the pattern in a string
    const char SUBJECT[] = "Hello, world!";
    rc = regEx.match(SUBJECT, sizeof(SUBJECT) - 1);
    if (rc == bdlpcre::RegEx::k_STATUS_SUCCESS) {
        bsl::cout << "Pattern matched!" << bsl::endl;
    }

    return 0;
}

Extracting Substrings Using Capture Groups

Regular expressions can be used to extract specific substrings from a larger string by defining capture groups. A capture group is a part of the regular expression pattern enclosed in parentheses. When a pattern is matched, the contents of the capture groups are stored in the match vector, allowing you to extract the captured substrings.

The following example demonstrates how to extract substrings using capture groups:

#include <bdlpcre_regex.h>
#include <bsl_iostream.h>
#include <bsl_vector.h>
#include <bsl_string.h>

using namespace BloombergLP;

int main() {
    bdlpcre::RegEx regEx;
    bsl::string errorMessage;
    size_t errorOffset;

    // Prepare the regular expression pattern with a capture group
    const char PATTERN[] = "Name: (\\w+)";
    int rc = regEx.prepare(&errorMessage, &errorOffset, PATTERN);
    if (rc != 0) {
        bsl::cerr << "Error preparing regex: " << errorMessage << bsl::endl;
        return rc;
    }

    // Match the pattern in a string
    const char SUBJECT[] = "Name: Alice";
    bsl::vector<bsl::pair<size_t, size_t>> matchVector;
    rc = regEx.match(&matchVector, SUBJECT, sizeof(SUBJECT) - 1);
    if (rc == bdlpcre::RegEx::k_STATUS_SUCCESS) {
        bsl::cout << "Pattern matched!" << bsl::endl;

        // Extract the captured group
        const auto& match = matchVector[1];  // First capture group
        bsl::string captured(SUBJECT + match.first, match.second);
        bsl::cout << "Captured name: " << captured << bsl::endl;
    }
    return 0;
}

Replacing Substrings

Regular expressions can also be used to replace substrings in a string with new content. The bdlpcre::RegEx class provides a convenient method for replacing matched substrings with new content:

#include <bdlpcre_regex.h>
#include <bsl_iostream.h>

using namespace BloombergLP;

int main() {
    bdlpcre::RegEx regEx;
    bsl::string errorMessage;
    size_t errorOffset;

    // Prepare the regular expression pattern
    const char PATTERN[] = "world";
    int rc = regEx.prepare(&errorMessage, &errorOffset, PATTERN);
    if (rc != 0) {
        bsl::cerr << "Error preparing regex: " << errorMessage << bsl::endl;
        return rc;
    }

    // Match the pattern in a string
    bsl::string_view SUBJECT = "Hello, world!";
    bsl::string_view REPLACEMENT = "universe";
    bsl::string result;
    int replaceErrorOffset;
    rc = regEx.replace(&result, &replaceErrorOffset, SUBJECT, REPLACEMENT);
    if (0 < rc) {
        bsl::cout << "Replaced string: " << result << bsl::endl;
    }

    return 0;
}

Simple benchmark

#include <bdlpcre_regex.h>
#include <bsl_iostream.h>
#include <bsl_string.h>
#include <chrono>
#include <regex>

using namespace BloombergLP;

int main() {
    bdlpcre::RegEx regEx;
    bsl::string errorMessage;
    size_t errorOffset;

    const char PATTERN[] = "\\b\\w+\\b";  // Match words
    const char SUBJECT[] = "This is a very simple benchmark test for regex";

    // bdlpcre::Regex
    // Compile the pattern
    auto start = std::chrono::high_resolution_clock::now();
    regEx.prepare(&errorMessage, &errorOffset, PATTERN, bdlpcre::RegEx::k_FLAG_JIT, 1024);
    auto end = std::chrono::high_resolution_clock::now();
    bsl::cout << "bdlpcre::RegEx Compile Time: "
            << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
            << " microseconds\n";

    // Match the pattern
    start = std::chrono::high_resolution_clock::now();
    regEx.match(SUBJECT, sizeof(SUBJECT) - 1);
    end = std::chrono::high_resolution_clock::now();
    bsl::cout << "bdlpcre::RegEx Match Time: "
            << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
            << " microseconds\n";

    // std::regex
    // Compile the pattern
    start = std::chrono::high_resolution_clock::now( );
    std::regex compiledPattern(PATTERN);
    end = std::chrono::high_resolution_clock::now();
    std::cout << "std::regex Compile Time: "
            << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
            << " microseconds\n";

    // Match the pattern
    start = std::chrono::high_resolution_clock::now();
    std::regex_match(SUBJECT, compiledPattern);
    end = std::chrono::high_resolution_clock::now();
    std::cout << "std::regex Match Time: "
            << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
            << " microseconds\n";

    return 0;
}
Check out the full documentation for

Happy coding!