用于匹配C++字符串常量的正则表达式

Nic*_*s M 6 c++ regex string c-preprocessor

我目前正在研究C++预处理器,我需要将字符串常量与超过0个字母匹配"hey I'm a string.我目前正在使用这个,\"([^\\\"]+|\\.)+\"但是我的一个测试用例失败了.

测试用例:

std::cout << "hello" << " world";
std::cout << "He said: \"bananas\"" << "...";
std::cout << "";
std::cout << "\x12\23\x34";
Run Code Online (Sandbox Code Playgroud)

预期产量:

std::cout << String("hello") << String(" world");
std::cout << String("He said: \"bananas\"") << String("...");
std::cout << "";
std::cout << String("\x12\23\x34");
Run Code Online (Sandbox Code Playgroud)

在第二个我反而得到

std::cout << String("He said: \")bananas\"String(" << ")...";
Run Code Online (Sandbox Code Playgroud)

短复制代码(使用AR.3的正则表达式):

std::string in_line = "std::cout << \"He said: \\\"bananas\\\"\" << \"...\";";
std::regex r("\"([^\"]+|\\.|(?<=\\\\)\")+\"");
in_line = std::regex_replace(in_line, r, "String($&)");
Run Code Online (Sandbox Code Playgroud)

Luc*_*ski 5

对源文件进行词法分析对于正则表达式来说一项很好的工作。但是对于这样的任务,让我们使用比std::regex. 让我们首先使用 PCRE(或boost::regex)。在这篇文章的最后,我将展示您可以使用功能较少的引擎做什么。

我们只需要进行部分词法分析,忽略所有不会影响字符串文字的无法识别的标记。我们需要处理的是:

  • 单行注释
  • 多行注释
  • 字符文字
  • 字符串文字

我们将使用扩展 ( x) 选项,它会忽略模式中的空格。

注释

内容如下[lex.comment]

字符/*开始注释,以字符 结束*/。这些注释不嵌套。字符//开始注释,在下一个换行符之前立即终止。如果这样的注释中有换页符或垂直制表符,则在它和终止注释的换行符之间只能出现空白字符;不需要诊断。[注意:注释字符//, /*, 和*///注释中没有特殊含义,与其他字符一样对待。同样,注释字符//和注释在注释中/*也没有特殊含义/*。— 尾注 ]

# singleline comment
// .* (*SKIP)(*FAIL)

# multiline comment
| /\* (?s: .*? ) \*/ (*SKIP)(*FAIL)
Run Code Online (Sandbox Code Playgroud)

十分简单。如果你在那里匹配任何东西,只是(*SKIP)(*FAIL)- 意味着你扔掉匹配。在(?s: .*? )该应用s(单线),修饰的.元字符,这意味着它允许匹配换行符。

字符文字

以下是语法[lex.ccon]

 character-literal:  
    encoding-prefix(opt) ’ c-char-sequence ’
  encoding-prefix:
    one of u8 u U L
  c-char-sequence:
    c-char
    c-char-sequence c-char
  c-char:
    any member of the source character set except the single-quote ’, backslash \, or new-line character
    escape-sequence
    universal-character-name
  escape-sequence:
    simple-escape-sequence
    octal-escape-sequence
    hexadecimal-escape-sequence
  simple-escape-sequence: one of \’ \" \? \\ \a \b \f \n \r \t \v
  octal-escape-sequence:
    \ octal-digit
    \ octal-digit octal-digit
    \ octal-digit octal-digit octal-digit
  hexadecimal-escape-sequence:
    \x hexadecimal-digit
    hexadecimal-escape-sequence hexadecimal-digit
Run Code Online (Sandbox Code Playgroud)

让我们先定义一些东西,稍后我们将需要它们:

(?(DEFINE)
  (?<prefix> (?:u8?|U|L)? )
  (?<escape> \\ (?:
    ['"?\\abfnrtv]         # simple escape
    | [0-7]{1,3}           # octal escape
    | x [0-9a-fA-F]{1,2}   # hex escape
    | u [0-9a-fA-F]{4}     # universal character name
    | U [0-9a-fA-F]{8}     # universal character name
  ))
)
Run Code Online (Sandbox Code Playgroud)
  • prefix被定义为一个可选的u8, u,UL
  • escape是按照标准定义的,只是universal-character-name为了简单起见我已经合并了

一旦我们有了这些,字符文字就非常简单了:

(?&prefix) ' (?> (?&escape) | [^'\\\r\n]+ )+ ' (*SKIP)(*FAIL)
Run Code Online (Sandbox Code Playgroud)

我们把它扔掉 (*SKIP)(*FAIL)

简单的字符串

它们的定义方式几乎与字符文字相同。这是其中的一部分[lex.string]

  string-literal:
    encoding-prefix(opt) " s-char-sequence(opt) "
    encoding-prefix(opt) R raw-string
  s-char-sequence:
    s-char
    s-char-sequence s-char
  s-char:
    any member of the source character set except the double-quote ", backslash \, or new-line character
    escape-sequence
    universal-character-name
Run Code Online (Sandbox Code Playgroud)

这将反映字符文字:

(?&prefix) " (?> (?&escape) | [^"\\\r\n]+ )* "
Run Code Online (Sandbox Code Playgroud)

区别在于:

  • 这次的字符序列是可选的(*而不是+
  • 未转义时不允许使用双引号而不是单引号
  • 我们实际上不会把它扔掉:)

原始字符串

这是原始字符串部分:

  raw-string:
    " d-char-sequence(opt) ( r-char-sequence(opt) ) d-char-sequence(opt) "
  r-char-sequence:
    r-char
    r-char-sequence r-char
  r-char:
    any member of the source character set, except a right parenthesis )
    followed by the initial d-char-sequence (which may be empty) followed by a double quote ".
  d-char-sequence:
    d-char
    d-char-sequence d-char
  d-char:
    any member of the basic source character set except:
    space, the left parenthesis (, the right parenthesis ), the backslash \,
    and the control characters representing horizontal tab,
    vertical tab, form feed, and newline.
Run Code Online (Sandbox Code Playgroud)

这个的正则表达式是:

(?&prefix) R " (?<delimiter>[^ ()\\\t\x0B\r\n]*) \( (?s:.*?) \) \k<delimiter> "
Run Code Online (Sandbox Code Playgroud)
  • [^ ()\\\t\x0B\r\n]*是分隔符 ( d-char)中允许的字符集
  • \k<delimiter> 指之前匹配的定界符

完整的图案

完整的模式是:

(?(DEFINE)
  (?<prefix> (?:u8?|U|L)? )
  (?<escape> \\ (?:
    ['"?\\abfnrtv]         # simple escape
    | [0-7]{1,3}           # octal escape
    | x [0-9a-fA-F]{1,2}   # hex escape
    | u [0-9a-fA-F]{4}     # universal character name
    | U [0-9a-fA-F]{8}     # universal character name
  ))
)

# singleline comment
// .* (*SKIP)(*FAIL)

# multiline comment
| /\* (?s: .*? ) \*/ (*SKIP)(*FAIL)

# character literal
| (?&prefix) ' (?> (?&escape) | [^'\\\r\n]+ )+ ' (*SKIP)(*FAIL)

# standard string
| (?&prefix) " (?> (?&escape) | [^"\\\r\n]+ )* "

# raw string
| (?&prefix) R " (?<delimiter>[^ ()\\\t\x0B\r\n]*) \( (?s:.*?) \) \k<delimiter> "
Run Code Online (Sandbox Code Playgroud)

请参阅此处的演示

boost::regex

这是一个简单的演示程序,使用boost::regex

#include <string>
#include <iostream>
#include <boost/regex.hpp>

static void test()
{
    boost::regex re(R"regex(
        (?(DEFINE)
          (?<prefix> (?:u8?|U|L) )
          (?<escape> \\ (?:
            ['"?\\abfnrtv]         # simple escape
            | [0-7]{1,3}           # octal escape
            | x [0-9a-fA-F]{1,2}   # hex escape
            | u [0-9a-fA-F]{4}     # universal character name
            | U [0-9a-fA-F]{8}     # universal character name
          ))
        )

        # singleline comment
        // .* (*SKIP)(*FAIL)

        # multiline comment
        | /\* (?s: .*? ) \*/ (*SKIP)(*FAIL)

        # character literal
        | (?&prefix)? ' (?> (?&escape) | [^'\\\r\n]+ )+ ' (*SKIP)(*FAIL)

        # standard string
        | (?&prefix)? " (?> (?&escape) | [^"\\\r\n]+ )* "

        # raw string
        | (?&prefix)? R " (?<delimiter>[^ ()\\\t\x0B\r\n]*) \( (?s:.*?) \) \k<delimiter> "
    )regex", boost::regex::perl | boost::regex::no_mod_s | boost::regex::mod_x | boost::regex::optimize);

    std::string subject(R"subject(
std::cout << L"hello" << " world";
std::cout << "He said: \"bananas\"" << "...";
std::cout << "";
std::cout << "\x12\23\x34";
std::cout << u8R"hello(this"is\a\""""single\\(valid)"
raw string literal)hello";

"" // empty string
'"' // character literal

// this is "a string literal" in a comment
/* this is
   "also inside"
   //a comment */

// and this /*
"is not in a comment"
// */

"this is a /* string */ with nested // comments"
    )subject");

    std::cout << boost::regex_replace(subject, re, "String\\($&\\)", boost::format_all) << std::endl;
}

int main(int argc, char **argv)
{
    try
    {
        test();
    }
    catch(std::exception ex)
    {
        std::cerr << ex.what() << std::endl;
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

(我禁用了语法高亮,因为它在这段代码上很疯狂)

出于某种原因,我不得不采取?量词出来的prefix(变化(?<prefix> (?:u8?|U|L)? )(?<prefix> (?:u8?|U|L) )(?&prefix)(?&prefix)?),使模式的工作原理。我相信这是 boost::regex 中的一个错误,因为 PCRE 和 Perl 在原始模式下都可以正常工作。

如果我们手头没有漂亮的正则表达式引擎怎么办?

请注意,虽然此模式在技术上使用递归,但它从不嵌套递归调用。通过将相关的可重用部分内联到主模式中,可以避免递归。

可以以降低性能为代价避免使用其他一些结构。我们可以安全地更换原子团(?>......)与正常对照组(?:......)如果我们为了避免不嵌套量词灾难性的回溯

(*SKIP)(*FAIL)如果我们在替换函数中添加一行逻辑,我们也可以避免:所有要跳过的替代项都分组在一个捕获组中。如果捕获组匹配,则忽略匹配。如果不是,那么它是一个字符串文字。

所有这一切意味着我们可以在 JavaScript 中实现它,它拥有您能找到的最简单的正则表达式引擎之一,代价是打破 DRY 规则并使模式难以辨认。一旦转换,正则表达式就变成了这个怪物:

(\/\/.*|\/\*[\s\S]*?\*\/|(?:u8?|U|L)?'(?:\\(?:['"?\\abfnrtv]|[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})|[^'\\\r\n])+')|(?:u8?|U|L)?"(?:\\(?:['"?\\abfnrtv]|[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})|[^"\\\r\n])*"|(?:u8?|U|L)?R"([^ ()\\\t\x0B\r\n]*)\([\s\S]*?\)\2"
Run Code Online (Sandbox Code Playgroud)

这是您可以玩的交互式演示:

function run() {
    var re = /(\/\/.*|\/\*[\s\S]*?\*\/|(?:u8?|U|L)?'(?:\\(?:['"?\\abfnrtv]|[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})|[^'\\\r\n])+')|(?:u8?|U|L)?"(?:\\(?:['"?\\abfnrtv]|[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})|[^"\\\r\n])*"|(?:u8?|U|L)?R"([^ ()\\\t\x0B\r\n]*)\([\s\S]*?\)\2"/g;
    
    var input = document.getElementById("input").value;
    var output = input.replace(re, function(m, ignore) {
        return ignore ? m : "String(" + m + ")";
    });
    document.getElementById("output").innerText = output;
}

document.getElementById("input").addEventListener("input", run);
run();
Run Code Online (Sandbox Code Playgroud)
<h2>Input:</h2>
<textarea id="input" style="width: 100%; height: 50px;">
std::cout << L"hello" << " world";
std::cout << "He said: \"bananas\"" << "...";
std::cout << "";
std::cout << "\x12\23\x34";
std::cout << u8R"hello(this"is\a\""""single\\(valid)"
raw string literal)hello";

"" // empty string
'"' // character literal

// this is "a string literal" in a comment
/* this is
   "also inside"
   //a comment */

// and this /*
"is not in a comment"
// */

"this is a /* string */ with nested // comments"
</textarea>
<h2>Output:</h2>
<pre id="output"></pre>
Run Code Online (Sandbox Code Playgroud)


Rol*_*lig 2

阅读 C++ 标准中的相关部分,它们称为lex.cconlex.string

然后将您找到的每个规则转换为正则表达式(如果您确实想使用正则表达式;可能会发现它们无法完成这项工作)。

然后,用它们构建更复杂的正则表达式。请务必严格按照 C++ 标准中的规则命名正则表达式,以便稍后重新检查它们。

如果您不想使用正则表达式,而是想使用现有工具,可以使用以下工具: http: //clang.llvm.org/doxygen/Lexer_8cpp_source.html。看看这个LexStringLiteral函数。

  • @LucasTrzesniewski C++ 原始字符串文字_不是_ Chomsky Type 3,因为反向引用 `\2`。因此传统的正则表达式无法匹配它们。增强的正则表达式(如 Perl、JavaScript)_可以_匹配它们,但是它们不再是_正则_了。 (2认同)