改 ECMAScript 正则表达式文法

来自cppreference.com
< cpp‎ | regex

此页面描述以设置为 ECMAScript (默认值)的 syntax_option_type 构造 std::basic_regex 时使用的文法。其他受支持正则表达式文法见 syntax_option_type

C++ 中的 ECMAScript 正则表达式文法是带标记有后述 (仅 C++) 的修改的 ECMA-262 文法

可选项

正则表达式模式是一或多个以析取运算符 | 分隔的可选项可选项 (Alternative) 序列(换言之,析取运算符拥有最低优先级)

Pattern ::

Disjunction

Disjunction ::

Alternative
Alternative | Disjunction

模式首先尝试跳过析取 (Disjunction) 并匹配(析取后的)后随剩余正则表达式的可选项

若它失败,则试图跳过左侧可选项并匹配右侧析取(后随剩余正则表达式)。

若左侧可选项、右侧析取和剩余正则表达式都拥有选择点,则在尝试移动到左侧可选项中的下个选择前,尝试剩余表达式值中的所有选择。若穷尽了左侧可选项中的所有选择,则取代左侧可选项尝试右侧析取

跳过的可选项内的任何捕获括号产生空子匹配。

#include <iostream>
#include <regex>
 
void show_matches(const std::string& in, const std::string& re)
{
    std::smatch m;
    std::regex_search(in, m, std::regex(re));
    if(m.empty()) {
        std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\n";
    } else {
        std::cout << "input=[" << in << "], regex=[" << re << "]: ";
        std::cout << "prefix=[" << m.prefix() << "] ";
        for(std::size_t n = 0; n < m.size(); ++n)
            std::cout << " m[" << n << "]=[" << m[n] << "] ";
        std::cout << "suffix=[" << m.suffix() << "]\n";
    }
}
int main()
{
    show_matches("abcdef", "abc|def");
    show_matches("abc", "ab|abc"); // 首先匹配左侧可选项
 
    // 针对后随剩余正则表达式 (c|bc) 左侧可选项 (a) 的匹配成功
    // 它生成 m[1]="a" 及 m[4]="bc" 。
    // 跳过的可选项 (ab) 和 (c) 将其子匹配 m[3] 和 m[5] 置为空。
    show_matches("abc", "((a)|(ab))((c)|(bc))");
}

输出:

input=[abcdef], regex=[abc|def]: prefix=[]  m[0]=[abc] suffix=[def]
input=[abc], regex=[ab|abc]: prefix=[]  m[0]=[ab] suffix=[c]
input=[abc], regex=[((a)|(ab))((c)|(bc))]: prefix=[]  m[0]=[abc]
m[1]=[a]  m[2]=[a]  m[3]=[] m[4]=[bc]  m[5]=[]  m[6]=[bc] suffix=[]

每个可选项为空,或为项 (Term) 的序列(间无分隔符)

Alternative ::

[empty]
Alternative Term

空的可选项始终匹配并且不消耗任何输入。

相继的尝试同时匹配输入的连续部分。

若左侧可选项、右侧和剩余正则表达式拥有选择点,则在移动到右侧中的下个选择前,尝试剩余正则表达式中的所有选择,并在移动到左侧可选项中的下个选择前,尝试右侧中的所有选择。

#include <iostream>
#include <regex>
 
void show_matches(const std::string& in, const std::string& re)
{
    std::smatch m;
    std::regex_search(in, m, std::regex(re));
    if(m.empty()) {
        std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\n";
    } else {
        std::cout << "input=[" << in << "], regex=[" << re << "]: ";
        std::cout << "prefix=[" << m.prefix() << "] ";
        for(std::size_t n = 0; n < m.size(); ++n)
            std::cout << " m[" << n << "]=[" << m[n] << "] ";
        std::cout << "suffix=[" << m.suffix() << "]\n";
    }
}
int main()
{
    show_matches("abcdef", ""); // 空正则表达式是单个空可选项
    show_matches("abc", "abc|"); // 左可选项首先匹配
    show_matches("abc", "|abc"); // 左可选项首先匹配,留待 abc 未匹配
}

输出:

input=[abcdef], regex=[]: prefix=[]  m[0]=[] suffix=[abcdef]
input=[abc], regex=[abc|]: prefix=[]  m[0]=[abc] suffix=[]
input=[abc], regex=[|abc]: prefix=[]  m[0]=[] suffix=[abc]

数量词

  • 每个断言 (Assertion)(见后方)或原子 (Atom) (见后方),或原子立即后随数量词 (Quantifier) 之一

Term ::

Assertion
Atom
Atom Quantifier

每个数量词贪心 (greedy) 数量词(仅由一个数量词前缀 (QuantifierPrefix) 组成)或非贪心 (non-greedy) 数量词(由一个数量词前缀后随问号掩码 ? 组成)。

Quantifier ::

QuantifierPrefix
QuantifierPrefix ?

每个数量词前缀确定二个数:最小重复数和最大重复数,如下:

数量词前缀 最小 最大
* 无穷大
+ 无穷大
?
{ 十进制数 } 十进制数的值 十进制数的值
{ 十进制数 , } 十进制数的值 无穷大
{ 十进制数 , 十进制数 } 逗号前的十进制数的值 逗号后的十进制数的值

通过在每个数位上调用 std::regex_traits::value(仅 C++)获得单独的十进制数 (DecimalDigits) 的值。

原子后随数量词重复数量词所指定的次数。数量词能为非贪心,该情况下原子模式重复在仍然匹配剩余正则表达式的同时尽可能少的次数,或能为贪心,该情况下原子模式重复在仍然匹配剩余正则表达式的同时金可鞥多的次数。

重复的是原子模式,而非其所匹配的输入,故原子的不同重复能匹配不同的输入子串。

原子和剩余正则表达式都有选择点,则首先将原子匹配尽可能尽可能多(或少,若为非贪心)次。,移动到原子的最后一次重复中的下个选择前,尝试剩余正则表达式中的所有选择。移动到原子的倒数第二(第 n-1 )次重复中的下个选择前,尝试原子的最后一(第 n )次重复中的所有选择;在明确现在可能有原子的更多或更少重复时;在移动到原子的第 n-1 次重复中的下个选择前,将这些穷尽(再次以尽可能少或多开始),以此类推。

每次重复原子'时,清除其捕获(见后方 "(z)((a+)?(b+)?(c))*" 的示例)

#include <iostream>
#include <regex>
 
void show_matches(const std::string& in, const std::string& re)
{
    std::smatch m;
    std::regex_search(in, m, std::regex(re));
    if(m.empty()) {
        std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\n";
    } else {
        std::cout << "input=[" << in << "], regex=[" << re << "]: ";
        std::cout << "prefix=[" << m.prefix() << "] ";
        for(std::size_t n = 0; n < m.size(); ++n)
            std::cout << " m[" << n << "]=[" << m[n] << "] ";
        std::cout << "suffix=[" << m.suffix() << "]\n";
    }
}
int main()
{
    // 贪心匹配,重复 [a-z] 4 次
    show_matches("abcdefghi", "a[a-z]{2,4}");
    // 非贪心匹配,重复 [a-z] 2 次
    show_matches("abcdefghi", "a[a-z]{2,4}?");
 
    // 数量词的选择点顺序,生成带二个重复的匹配,
    // 第一个匹配子串 "aa" ,第二个匹配子串 "ba" ,保留 "ac" 匹配
    // ("ba" 出现于 m[1] 的捕获子句中)
    show_matches("aabaac", "(aa|aabaac|ba|b|c)*");
 
    // 数量词的选择点顺序令此 regex 计算 10 与 15 间的最大公约数
    // (答案是 5 ,并以 "aaaaa" 填充 m[1] )
    show_matches("aaaaaaaaaa,aaaaaaaaaaaaaaa", "^(a+)\\1*,\\1+$");
 
    // 子串 "bbb" 不出现于捕获子句 m[4] 中
    // 因为它在原子 (a+)?(b+)?(c) 的第二次重复匹配子串 "ac" 时被声明
    show_matches("zaacbbbcac", "(z)((a+)?(b+)?(c))*");
}

输出:

input=[abcdefghi], regex=[a[a-z]{2,4}]: prefix=[]  m[0]=[abcde] suffix=[fghi]
input=[abcdefghi], regex=[a[a-z]{2,4}?]: prefix=[]  m[0]=[abc] suffix=[defghi]
input=[aabaac], regex=[(aa|aabaac|ba|b|c)*]:
 prefix=[]  m[0]=[aaba]  m[1]=[ba] suffix=[ac]
input=[aaaaaaaaaa,aaaaaaaaaaaaaaa], regex=[^(a+)\1*,\1+$]:
 prefix=[]  m[0]=[aaaaaaaaaa,aaaaaaaaaaaaaaa]  m[1]=[aaaaa] suffix=[]
input=[zaacbbbcac], regex=[(z)((a+)?(b+)?(c))*]:
prefix=[]  m[0]=[zaacbbbcac]  m[1]=[z]  m[2]=[ac]  m[3]=[a]  m[4]=[]  m[5]=[c] suffix=[]

断言

断言 (Assertion) 匹配条件,而非输入字符串的子串。它们决不消耗任何来自输入的字符。每个断言是下列之一

Assertion ::

^
$
\ b
\ B
( ? = Disjunction )
( ? ! Disjunction )

断言 ^ (行起始)匹配

1) 立即后随行终止符 (LineTerminator) 字符的位置(这可能不受支持) (C++17 前)(这仅若启用 std::regex_constants::multiline(仅 C++) 才得到保证) (C++17 起)
2) 输入的起始(除非启用 std::regex_constants::match_not_bol(仅 C++)

断言 $ (行结尾)匹配

1) 行终止符字符的位置(这可能不受支持) (C++17 前)(这仅若启用 std::regex_constants::multiline(仅 C++) 才得到保证) (C++17 起)
2) 输入的结尾(除非启用 std::regex_constants::match_not_bol(仅 C++)

上面二个断言和下面的原子 . 中,行终止符是下列四个字符之一: U+000A\n 或换行)、 U+000D\r 或回车)、 U+2028 (行分隔符)或 U+2029 (段分隔符)

断言 \b (词边界)匹配

1) 词的起始(当前字符为字母、数字或下划线,而前一字符不是)
2) 词的结尾(当前字符不是字母、数字或下划线,而前一字符是这些之一)
3) 输入的起始,若首字符为字母、数字或下划线(除非启用 std::regex_constants::match_not_bow(仅 C++)
4) 输入的结尾,若末字符为字母、数字或下划线(除非启用 std::regex_constants::match_not_eow(仅 C++)

断言 \B (反词边界)匹配所有字符,除了下列内容

1) 词的起始(当前字符为字母、数字或下划线,而前一字符不是这些之一或不存在)
2) 词的结尾(当前字符不是字母、数字或下划线(或匹配者在输入结尾),前一字符是这些之一)

析取会匹配在当前位置的输入,则断言 ( ? = 析取 ) (零宽正前瞻)匹配。

析取会匹配在当前位置的的输入,则断言 ( ? ! 析取 ) (零宽负前瞻)匹配。

对于两个前瞻断言,在匹配析取时,不在匹配剩余正则表达式之前令位置前进。另外,若析取能以多种方式在当前位置匹配,则只尝试第一个。

ECMAScript 禁止回撤到前瞻析取中,这影响到来自剩余正则表达式的正前瞻中的回溯引用(见下方示例) 。到来自剩余正则表达式的负前瞻中的回溯引用始终没有定义(因为前瞻断言必定无法继续)。

注意:前瞻断言可用于创建多个正则表达式间的逻辑与(见下方示例)。

#include <iostream>
#include <regex>
 
void show_matches(const std::string& in, const std::string& re)
{
    std::smatch m;
    std::regex_search(in, m, std::regex(re));
    if(m.empty()) {
        std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\n";
    } else {
        std::cout << "input=[" << in << "], regex=[" << re << "]: ";
        std::cout << "prefix=[" << m.prefix() << "] ";
        for(std::size_t n = 0; n < m.size(); ++n)
            std::cout << " m[" << n << "]=[" << m[n] << "] ";
        std::cout << "suffix=[" << m.suffix() << "]\n";
    }
}
int main()
{
    // 在输入结尾匹配 a
    show_matches("aaa", "a$");
 
    // 在第一个词结尾匹配 o
    show_matches("moo goo gai pan", "o\\b");
 
    // 前瞻匹配立即在第一个 b 之后的空字符串
    // 这以 "aaa" 填充 m[1] ,尽管 m[0] 为空
    show_matches("baaabac", "(?=(a+))");
 
    // 因为禁止回溯引用回撤到前瞻中,
    // 这匹配 aba 而非 aaaba
    show_matches("baaabac", "(?=(a+))a*b\\1");
 
    // 经由前瞻的逻辑与:此密码匹配,若它含有
    // 至少一个小写字母
    // 与 至少一个大写字母
    // 与 至少一个标点字符
    // 与 至少有 6 个字符长
    show_matches("abcdef", "(?=.*[[:lower:]])(?=.*[[:upper:]])(?=.*[[:punct:]]).{6,}");
    show_matches("aB,def", "(?=.*[[:lower:]])(?=.*[[:upper:]])(?=.*[[:punct:]]).{6,}");
}

输出:

input=[aaa], regex=[a$]: prefix=[aa]  m[0]=[a] suffix=[]
input=[moo goo gai pan], regex=[o\b]: prefix=[mo]  m[0]=[o] suffix=[ goo gai pan]
input=[baaabac], regex=[(?=(a+))]: prefix=[b]  m[0]=[]  m[1]=[aaa] suffix=[aaabac]
input=[baaabac], regex=[(?=(a+))a*b\1]: prefix=[baa]  m[0]=[aba]  m[1]=[a] suffix=[c]
input=[abcdef], regex=[(?=.*[[:lower:]])(?=.*[[:upper:]])(?=.*[[:punct:]]).{6,}]:
 NO MATCH
input=[aB,def], regex=[(?=.*[[:lower:]])(?=.*[[:upper:]])(?=.*[[:punct:]]).{6,}]:
 prefix=[]  m[0]=[aB,def] suffix=[]

原子

原子能为下列之一:

Atom ::

PatternCharacter
.
\ AtomEscape
CharacterClass
( Disjunction )
( ? : Disjunction )

其中 AtomEscape ::

DecimalEscape
CharacterEscape
CharacterClassEscape

不同种类的原子求值方式不同。

子表达式

原子 ( 析取 ) 是有标记表达式:它执行析取并存储析取所消耗的输入子串于子匹配数组,下标对应在此点已遇到的整个正则表达式中,有标记子表达式的左开括号 ( 次数。

在返回于 std::match_results 之外,捕获的子匹配可作为回溯引用( \1\2 ……)访问,而且能在 std::regex_replace 中引用。

原子 ( ? : 析取 ) (非标记子表达式)简单地求值析取且不存储其结果于子匹配。这是纯的词汇分组。

回溯引用

DecimalEscape ::

DecimalIntegerLiteral [lookaheadDecimalDigit]

\ 后随首位非 0 的十进制数 N ,则认为该转义序列为回溯引用 (backreference) 。通过在每个数位上调用 std::regex_traits::value(仅 C++) 并用底 10 算术组合及其结果获得 N 。若 N 大于整个正则表达式中捕获括号的总数,则为错误。

回溯引用 \N 作为原子出现时,它匹配当前存储于子匹配数组中第 N 个元素的子串。

十进制转义 \0 是回溯引用:它是表示空字符的字符转义。它不能为十进制数所后随。

单字符匹配

原子 . 匹配并消耗来自输入序列的任一字符,除了行终止符U+000AU+000DU+2028U+2029

原子 模式字符 ,其中模式字符 (PatternCharacter) 为任何源字符 (SourceCharacter)除了字符 ^ $ \ . * + ? ( ) [ ] { } | ,匹配并消耗一个来自输入的字符,若它等于此模式字符

这个及所有其他单字符匹配定义如下:

1) 若设置了 std::regex_constants::icase ,则若 std::regex_traits::translate_nocase 的返回值相等则字符相等。(仅 C++)
2) 否则,若设置了 std::regex_constants::collate ,则若 std::regex_traits::translate 的返回值相等则字符相等。(仅 C++)
3) 否则,若 operator== 返回 true 则字符相等。

每个由转义字符 \ 后随字符转义 (CharacterEscape)原子,还有特殊十进制转义 \0 ,匹配消耗一个来自输入的字符,若它等于字符转义所表示的字符。辨识下列字符转义序列:

CharacterEscape ::

ControlEscape
c ControlLetter
HexEscapeSequence
UnicodeEscapeSequence
IdentityEscape

此处控制转义 (ControlEscape) 为下列五个字符之一: f n r t v

控制转义 编码单元 名称
f U+000C 换页
n U+000A 换行
r U+000D 回车
t U+0009 水平制表
v U+000B 垂直制表

控制字母 (ControlLetter) 为任何小写或大写 ASCII 字符,而此字符转义所匹配字符的编码单元等于控制字母的编码单元的值除以 32 ,例如 \cD\cd 都匹配编码单元 U+0004 (EOT) ,因为 'D' 是 U+00440x44 % 32 == 4 并且 'd' 是 U+00640x64 % 32 == 4

十六进制转义序列 (HexEscapeSequence) 是字母 x 后随准确二个十六进制位 (HexDigit) (其中十六进制位0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F 之一)。此字符转义所匹配字符的编码单元等于二位十六进制数的数值。

Unicode转义序列 (UnicodeEscapeSequence) 是字母 u 后随准确四个十六进制位。此字符转义所匹配字符的编码单元等于此四位十六进制数的数值。若该值不适于此 std::basic_regex 的 CharT ,则抛出 std::regex_error (仅 C++)

自身转义 (IdentityEscape) 能为任何非字母数字的字符:例如另一反斜杠。它照原样匹配字符。

字符类

原子能表示字符类,即它会匹配并消耗一个字符,若该字符属于预定义的字符组之一。

字符类能通过字符类转义引入:

Atom ::

\ CharacterClassEscape

或直接为

Atom ::

CharacterClass

字符类转义是一些常用字符类的简洁写法,如下:

字符类转义 类名表达式(仅 C++) 含义
d [[:digit:]] 数字
D [^[:digit:]] 非数字
s [[:space:]] 空白字符
S [^[:space:]] 非空白字符
w [_[:alnum:]] 字母数字字符及字符 _
W [^_[:alnum:]] 异于字母数字或 _ 的字符
C++ 中这些字符类转义的准确含义以依赖 locale 的具名字符类定义,而非通过显式列举同 ECMAScript 中的可接受字符。

字符类 (CharacterClass) 是方括号环绕的类范围 (ClassRanges) 序列,可选地以取反运算符 ^ 开始。若它始于 ^ ,则此原子匹配任何不在所有类范围的并所表示的字符集合中的字符。否则,此原子匹配任何所有类范围的并所表示的字符集合中的字符。

CharacterClass ::

[ [ lookahead ∉ {^}] ClassRanges ]
[ ^ ClassRanges ]

ClassRanges ::

[empty]
NonemptyClassRanges

NonemptyClassRanges ::

ClassAtom
ClassAtom NonemptyClassRangesNoDash
ClassAtom - ClassAtom ClassRanges

若非空类范围拥有形式 ClassAtom - ClassAtom ,则它匹配来自定义如下的范围的任何字符:(仅 C++)

首个类原子 (ClassAtom) 必须匹配单个对照元素 c1 ,而第二个类原子必须匹配单个对照元素 c2 。采用下列步骤,测试此范围是否匹配输入字符 c

1)std::regex_constants::collate 不打开,则通过直接比较编码点匹配字符:若 c1 <= c && c <= c2 则匹配 c
1) 否则(若启用 std::regex_constants::collate ):
1) 若启用 std::regex_constants::icase ,则传递所有三个字符( cc1c2 )给 std::regex_traits::translate_nocase
2) 否则(若未设置 std::regex_constants::icase ),则传递所有三个字符( cc1c2 )给 std::regex_traits::translate
2)std::regex_traits::transform 比较结果字符串,而若 transformed c1 <= transformed c && transformed c <= transformed c2 则匹配 c

按字面对待字符 - ,若它是下列之一

  • 类范围的首或末字符
  • 杠分隔范围规定的开始或结尾类原子
  • 立即在杠分隔范围规定之后。
  • 以反斜杠转义为字符转义

NonemptyClassRangesNoDash ::

ClassAtom
ClassAtomNoDash NonemptyClassRangesNoDash
ClassAtomNoDash - ClassAtom ClassRanges

ClassAtom ::

-
ClassAtomNoDash
ClassAtomExClass(仅 C++)
ClassAtomCollatingElement(仅 C++)
ClassAtomEquivalence(仅 C++)

ClassAtomNoDash ::

SourceCharacter but not one of \ or ] or -
\ ClassEscape

每个无杠类原子 (ClassAtomNoDash) 表示单个字符——原状的源字符或转义如下的字符:

ClassEscape ::

DecimalEscape
b
CharacterEscape
CharacterClassEscape

特殊的类转义 (ClassEscape) \b 产生匹配编码单元 U+0008 (退格)的字符集。在字符类外,它是词边界断言

字符类内, \B 的使用和任何回溯引用(异于零的十进制转义)都是错误。

为将字符 -] 当做原子,一些情形中需要转义它们。其他拥有在字符类外的特殊含义的字符,例如 *? ,不需要转义。

基于 POSIX 的字符类

这些字符类是对 ECMAScript 文法的扩展,并等价于 POSIX 正则表达式中找到的字符类。

ClassAtomExClass(仅 C++) ::

[: ClassName :]

表示所有具名字符类类名 (ClassName) 中的成员。仅若 std::regex_traits::lookup_classname 对此名称返回非零字符串,名称才合法。如 std::regex_traits::lookup_classname 中描述,保证辨识下列名称: alnum, alpha, blank, cntrl, digit, graph, lower, print, punct, space, upper, xdigit, d, s, w 。额外的名称(如日文中的 jdigitjkanji )可为系统提供的 locale 提供,或实现为用户定义的扩展:

ClassAtomCollatingElement(仅 C++) ::

[. ClassName .]

表示具名对照元素,它可表示单个字符,或在感染的 locale 下作为单个单位对照的字符序列,例如 [.tilde.] 或捷克文中的 [.ch.] 。仅若 std::regex_traits::lookup_collatename 不是空字符串名称才合法。

使用 std::regex_constants::collate 时,始终能以对照元素为范围的端点(例如匈牙利文中的 [[.dz.]-g] )。

ClassAtomEquivalence(仅 C++) ::

[= ClassName =]

表示与具名对照元素相同的等价类的所有成员字符,即其初等对照关键与对照元素类名所拥有者相同的字符。仅若 std::regex_traits::lookup_collatename 对该名称返回非空字符串,且 std::regex_traits::transform_primary 对调用 std::regex_traits::lookup_collatename 的结果的返回值不是空字符串,名称才合法。

初等排序关键是忽略大小写、标音符或本地环境限定裁剪的关键;故例如 [[=a=]] 匹配任何这些字符之一: a, À, Á, Â, Ã, Ä, Å, A, à, á, â, ã, äå

ClassName(仅 C++) ::

ClassNameCharacter
ClassNameCharacter ClassName

ClassNameCharacter(仅 C++) ::

SourceCharacter 但非 . = : 之一