改 ECMAScript 正则表达式文法
此页面描述以设置为 ECMAScript
(默认值)的 syntax_option_type 构造 std::basic_regex 时使用的文法。其他受支持正则表达式文法见 syntax_option_type 。
C++ 中的 ECMAScript
3 正则表达式文法是带标记有后述 (仅 C++) 的修改的 ECMA-262 文法。
总览
改正则表达式文法几乎是 ECMAScript RegExp 文法带上类原子 (ClassAtom) 下的本地环境上的 POSIX 类型展开。我们作出了相等检查与数量分析上的一些澄清。对于此处的多数示例,你可以在你的浏览器控制台中尝试此等价版本:
function match(s, re) { return s.match(new RegExp(re)); }
标准中的“正式引用”指定 ECMAScript 3 。我们于此链接到 ECMAScript 5.1 规范,因为其正则表达式文法相比 ECMAScript 3 仅有微小改动,而且它有一个 HTML 版本。方言特性的概览见 MDN Guide on JavaScript RegExp 。
可选项
正则表达式模式是一或多个以析取运算符 |
分隔的可选项可选项 (Alternative) 序列(换言之,析取运算符拥有最低优先级)
Pattern ::
- Disjunction
Disjunction ::
- Alternative
- Alternative
|
Disjunction
模式首先尝试跳过析取 (Disjunction) 并匹配(析取后的)后随剩余正则表达式的可选项。
若它失败,则试图跳过左侧可选项并匹配右侧析取(后随剩余正则表达式)。
若左侧可选项、右侧析取和剩余正则表达式都拥有选择点,则在尝试移动到左侧可选项中的下个选择前,尝试剩余表达式值中的所有选择。若穷尽了左侧可选项中的所有选择,则取代左侧可选项尝试右侧析取。
跳过的可选项内的任何捕获括号产生空子匹配。
#include <iostream> #include <regex> // TODO :转换到接受宽字符序列 // std::match_results<std::basic_string<_ch>::const_iterator> m; 不行。 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> // TODO :转换到接受宽字符序列 // std::match_results<std::basic_string<_ch>::const_iterator> m; 不行。 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> // TODO :转换到接受宽字符序列 // std::match_results<std::basic_string<_ch>::const_iterator> m; 不行。 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" 时被声明 // 注: gcc 理解有误——它没有正确地按 ECMA-262 21.2.2.5.1 清除 // matches[4] 捕获组,从而错误地对于该组捕获 "bbb" 。 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)
断言 ^
(行起始)匹配
断言 $
(行结尾)匹配
上面二个断言和下面的原子 .
中,行终止符是下列四个字符之一: U+000A
( \n
或换行)、 U+000D
( \r
或回车)、 U+2028
(行分隔符)或 U+2029
(段分隔符)
断言 \b
(词边界)匹配
断言 \B
(反词边界)匹配所有字符,除了下列内容
若析取会匹配在当前位置的输入,则断言 (
?
=
析取 )
(零宽正前瞻)匹配。
若析取不会匹配在当前位置的的输入,则断言 (
?
!
析取 )
(零宽负前瞻)匹配。
对于两个前瞻断言,在匹配析取时,不在匹配剩余正则表达式之前令位置前进。另外,若析取能以多种方式在当前位置匹配,则只尝试第一个。
ECMAScript 禁止回撤到前瞻析取中,这影响到来自剩余正则表达式的正前瞻中的回溯引用(见下方示例) 。到来自剩余正则表达式的负前瞻中的回溯引用始终没有定义(因为前瞻断言必定无法继续)。
注意:前瞻断言可用于创建多个正则表达式间的逻辑与(见下方示例)。
#include <iostream> #include <regex> // TODO :转换到接受宽字符序列 // std::match_results<std::basic_string<_ch>::const_iterator> m; 不行。 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 以同 String.prototype.replace (ECMA-262, part 15.5.4.11) 的方式,对于回溯引用以 $
代替 \
( $1
、 $2
……)。
原子 (
?
:
析取 )
(非标记子表达式)简单地求值 析取 并不存储其结果于子匹配。这单纯是词法分组。
本节未完成 原因:暂无示例 |
回溯引用
DecimalEscape ::
- DecimalIntegerLiteral [lookahead ∉ DecimalDigit]
若 \
后随首位非 0
的十进制数 N
,则认为该转义序列为回溯引用 (backreference) 。通过在每个数位上调用 std::regex_traits::value(仅 C++) 并用底 10 算术组合及其结果获得 N
。若 N
大于整个正则表达式中捕获括号的总数,则为错误。
回溯引用 \N
作为原子出现时,它匹配当前存储于子匹配数组中第 N 个元素的子串。
十进制转义 \0
不是回溯引用:它是表示空字符的字符转义。它不能为十进制数所后随。
如上,注意 std::regex_replace 对于回溯引用以 $
代替 \
( $1
、 $2
……)。
本节未完成 原因:暂无示例 |
单字符匹配
原子 .
匹配并消耗来自输入序列的任一字符,除了行终止符( U+000A
、 U+000D
、 U+2028
或 U+2029
)
原子 模式字符 ,其中模式字符 (PatternCharacter) 为任何源字符 (SourceCharacter) ,除了字符 ^ $ \ . * + ? ( ) [ ] { } |
,匹配并消耗一个来自输入的字符,若它等于此模式字符。
这个及所有其他单字符匹配定义如下:
每个由转义字符 \
后随字符转义 (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+0044
而 0x44 % 32 == 4
并且 'd' 是 U+0064
而 0x64 % 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) 能为任何非字母数字的字符:例如另一反斜杠。它照原样匹配字符。
#include <iostream> #include <regex> // TODO :转换到接受宽字符序列 // std::match_results<std::basic_string<_ch>::const_iterator> m; 不行。 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() { // 大多数转义类似 C++ ,为元字符保存。你将需要在斜杠情形使用双重转义或未处理字符串。 show_matches("C++\\", R"(C\+\+\\)"); // 转义序列与 NUL 。 std::string s("ab\xff\0cd", 5); show_matches(s, "(\\0|\\u00ff)"); // 没有定义非 BMP Unicode 的匹配,因为 ECMAScript 使用 UTF-16 原子。 // 此 emoji 香蕉是否匹配能为平台依赖: // XXX :这些需要为宽字符串! // show_matches(L"\U0001f34c", L"[\\u0000-\\ufffe]+"); }
字符类
原子能表示字符类,即它会匹配并消耗一个字符,若该字符属于预定义的字符组之一。
字符类能通过字符类转义引入:
Atom ::
-
\
CharacterClassEscape
或直接为
Atom ::
- CharacterClass
字符类转义是一些常用字符类的简洁写法,如下:
字符类转义 | 类名表达式(仅 C++) | 含义 |
---|---|---|
d
|
[[:digit:]]
|
数字 |
D
|
[^[:digit:]]
|
非数字 |
s
|
[[:space:]]
|
空白字符 |
S
|
[^[:space:]]
|
非空白字符 |
w
|
[_[:alnum:]]
|
字母数字字符及字符 _
|
W
|
[^_[:alnum:]]
|
异于字母数字或 _ 的字符
|
字符类 (CharacterClass) 是方括号环绕的类范围 (ClassRanges) 序列,可选地以取反运算符 ^
开始。若它始于 ^
,则此原子匹配任何不在所有类范围的并所表示的字符集合中的字符。否则,此原子匹配任何在所有类范围的并所表示的字符集合中的字符。
CharacterClass ::
-
[
[
lookahead ∉ {^
}] ClassRanges]
-
[
^
ClassRanges]
ClassRanges ::
- [empty]
- NonemptyClassRanges
NonemptyClassRanges ::
- ClassAtom
- ClassAtom NonemptyClassRangesNoDash
- ClassAtom - ClassAtom ClassRanges
若非空类范围拥有形式 ClassAtom - ClassAtom
,则它匹配来自定义如下的范围的任何字符:(仅 C++)
首个类原子 (ClassAtom) 必须匹配单个对照元素 c1
,而第二个类原子必须匹配单个对照元素 c2
。采用下列步骤,测试此范围是否匹配输入字符 c
:
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
。额外的名称(如日文中的 jdigit
或 jkanji
)可为系统提供的 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 但非
. = :
之一
本节未完成 原因:暂无示例 |