有改动的 ECMAScript 正则表达式文法
此页面描述以设置为 ECMAScript
(默认值)的 syntax_option_type
构造 std::basic_regex 时使用的正则表达式文法。其他受支持的正则表达式文法见 syntax_option_type
。
C++ 中的 ECMAScript
3 正则表达式文法是 ECMA-262 语法,并带有一些修改,下文将之标记为 (仅 C++)。
总览
有改动的正则表达式文法几乎是 ECMAScript RegExp 文法,并带有类原子 下进行本地环境上的 POSIX 类型展开。我们作出了相等检查与数量分析上的一些澄清。对于这里的多数示例,你可以在你的浏览器控制台中尝试等价的版本:
function match(s, re) { return s.match(new RegExp(re)); }
标准中的“正式参考”指定 ECMAScript 3。我们这里链接到 ECMAScript 5.1 规范,因为它的正则表达式语法相比 ECMAScript 3 仅有微小改动,而且它有一个 HTML 版本。方言特性的概览见 MDN Guide on JavaScript RegExp。
可选项
正则表达式模式是一或多个以析取运算符 |
分隔的可选项 的序列(换言之,析取运算符拥有最低优先级)
模式 ::
- 析取
析取 ::
- 可选项
- 可选项
|
析取
模式首先尝试跳过析取 并匹配(析取后的)后随剩余正则表达式的左侧可选项。
如果它失败,那么尝试跳过左侧可选项 并匹配右侧析取(后随剩余正则表达式)。
如果左侧可选项、右侧析取 和剩余正则表达式都拥有选择点,那么在尝试移动到左侧可选项 中的下个选择前,尝试剩余表达式值中的所有选择。如果穷尽了左侧可选项 中的所有选择,那么取代左侧可选项 尝试右侧析取。
跳过的可选项 内的任何捕获括号产生空子匹配。
#include <cstddef> #include <iostream> #include <regex> #include <string> 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 << "]\n " "prefix=[" << m.prefix() << "]\n smatch: "; for (std::size_t n = 0; n < m.size(); ++n) std::cout << "m[" << n << "]=[" << m[n] << "] "; std::cout << "\n suffix=[" << m.suffix() << "]\n"; } else std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\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=[] smatch: m[0]=[abc] suffix=[def] input=[abc], regex=[ab|abc] prefix=[] smatch: m[0]=[ab] suffix=[c] input=[abc], regex=[((a)|(ab))((c)|(bc))] prefix=[] smatch: m[0]=[abc] m[1]=[a] m[2]=[a] m[3]=[] m[4]=[bc] m[5]=[] m[6]=[bc] suffix=[]
项
每个可选项 要么为空,要么是项 的序列(项 间无分隔符)
可选项 ::
- [空]
- 可选项 项
空的可选项 始终匹配并且不消耗任何输入。
相继的项 尝试同时匹配输入的连续部分。
如果左侧可选项 、右侧项 和剩余正则表达式都拥有选择点,那么在移动到右侧项 中的下个选择前,尝试剩余正则表达式中的所有选择,并在移动到左侧可选项 中的下个选择前,尝试右侧项 中的所有选择。
#include <cstddef> #include <iostream> #include <regex> #include <string> 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 << "]\n " "prefix=[" << m.prefix() << "]\n smatch: "; for (std::size_t n = 0; n < m.size(); ++n) std::cout << "m[" << n << "]=[" << m[n] << "] "; std::cout << "\n suffix=[" << m.suffix() << "]\n"; } else std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\n"; } int main() { show_matches("abcdef", ""); // 空正则表达式是单个空可选项 show_matches("abc", "abc|"); // 左可选项首先匹配 show_matches("abc", "|abc"); // 左可选项首先匹配,留待 abc 未匹配 }
输出:
input=[abcdef], regex=[] prefix=[] smatch: m[0]=[] suffix=[abcdef] input=[abc], regex=[abc|] prefix=[] smatch: m[0]=[abc] suffix=[] input=[abc], regex=[|abc] prefix=[] smatch: m[0]=[] suffix=[abc]
数量词
- 每个项 是断言(见下文)、原子(见下文),和原子 立即后随数量词 之一
项 ::
- 断言
- 原子
- 原子 数量词
每个数量词 要么是贪心 数量词(仅由一个数量词前缀 组成),要么是非贪心 数量词(由一个数量词前缀 后随问号 ?
组成)。
数量词 ::
- 数量词前缀
- 数量词前缀
?
每个数量词前缀 确定两个数:最小重复数和最大重复数,如下:
数量词前缀 | 最小 | 最大 |
---|---|---|
*
|
零 | 无穷大 |
+
|
一 | 无穷大 |
?
|
零 | 一 |
{ 十进制数 }
|
十进制数的值 | 十进制数的值 |
{ 十进制数 , }
|
十进制数的值 | 无穷大 |
{ 十进制数 , 十进制数 }
|
逗号前的十进制数的值 | 逗号后的十进制数的值 |
通过在每个数位上调用 std::regex_traits::value(仅 C++)获得单独的十进制数 的值。
原子 后随数量词 重复数量词 所指定的次数。数量词 可以是非贪心 的,此时原子 模式重复在仍然匹配剩余正则表达式的同时尽可能少的次数;也可以是贪心 的,此时原子 模式重复在仍然匹配剩余正则表达式的同时尽可能多的次数。
重复的是原子 模式,而非它匹配的输入,因此原子 的不同重复能匹配不同的输入子串。
如果原子 和剩余正则表达式都有选择点,那么首先将原子 匹配尽可能多(或少,如果是非贪心 的)次。,移动到原子 的最后一次重复中的下个选择前,尝试剩余正则表达式中的所有选择。移动到原子 的倒数第二(第 n-1)次重复中的下个选择前,尝试原子 的最后一(第 n)次重复中的所有选择;在明确现在可能有原子 的更多或更少重复时;在移动到原子 的第 n-1 次重复中的下个选择前,将这些穷尽(再次以尽可能少或多开始),以此类推。
每次重复原子 时,清除它的捕获(见下文 "(z)((a+)?(b+)?(c))*" 的示例)
#include <cstddef> #include <iostream> #include <regex> #include <string> 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 << "]\n " "prefix=[" << m.prefix() << "]\n smatch: "; for (std::size_t n = 0; n < m.size(); ++n) std::cout << "m[" << n << "]=[" << m[n] << "] "; std::cout << "\n suffix=[" << m.suffix() << "]\n"; } else std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\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=[] smatch: m[0]=[abcde] suffix=[fghi] input=[abcdefghi], regex=[a[a-z]{2,4}?] prefix=[] smatch: m[0]=[abc] suffix=[defghi] input=[aabaac], regex=[(aa|aabaac|ba|b|c)*] prefix=[] smatch: m[0]=[aaba] m[1]=[ba] suffix=[ac] input=[aaaaaaaaaa,aaaaaaaaaaaaaaa], regex=[^(a+)\1*,\1+$] prefix=[] smatch: m[0]=[aaaaaaaaaa,aaaaaaaaaaaaaaa] m[1]=[aaaaa] suffix=[] input=[zaacbbbcac], regex=[(z)((a+)?(b+)?(c))*] prefix=[] smatch: m[0]=[zaacbbbcac] m[1]=[z] m[2]=[ac] m[3]=[a] m[4]=[] m[5]=[c] suffix=[]
断言
断言 匹配条件,而非输入字符串的子串。它们不会消耗任何来自输入的字符。每个断言 是下列之一
断言 ::
-
^
-
$
-
\
b
-
\
B
-
(
?
=
析取)
-
(
?
!
析取)
断言 ^
(行起始)匹配
断言 $
(行结尾)匹配
在上面两个断言和下面的原子 .
中,行终止符 是下列四个字符之一: U+000A
(\n
或换行)、U+000D
(\r
或回车)、U+2028
(行分隔符)或 U+2029
(段分隔符)
断言 \b
(词边界)匹配
断言 \B
(反词边界)匹配所有字符,除了下列内容
如果析取 会匹配在当前位置的输入,那么断言 (
?
=
析取 )
(零宽正前瞻)匹配。
如果析取 不会匹配在当前位置的的输入,那么断言 (
?
!
析取 )
(零宽负前瞻)匹配。
对于两个前瞻断言,在匹配析取 时,不在匹配剩余正则表达式之前令位置前进。另外,如果析取 能以多种方式在当前位置匹配,那么只尝试第一个。
ECMAScript 禁止回撤到前瞻析取中,这影响到来自剩余正则表达式的正前瞻中的回溯引用(见下方示例)。到来自剩余正则表达式的负前瞻中的回溯引用始终没有定义(因为前瞻断言必定无法继续)。
注意:前瞻断言可用于创建多个正则表达式间的逻辑与(见下方示例)。
#include <cstddef> #include <iostream> #include <regex> #include <string> 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 << "]\n " "prefix=[" << m.prefix() << "]\n smatch: "; for (std::size_t n = 0; n < m.size(); ++n) std::cout << "m[" << n << "]=[" << m[n] << "] "; std::cout << "\n suffix=[" << m.suffix() << "]\n"; } else std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\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] smatch: m[0]=[a] suffix=[] input=[moo goo gai pan], regex=[o\b] prefix=[mo] smatch: m[0]=[o] suffix=[ goo gai pan] input=[baaabac], regex=[(?=(a+))] prefix=[b] smatch: m[0]=[] m[1]=[aaa] suffix=[aaabac] input=[baaabac], regex=[(?=(a+))a*b\1] prefix=[baa] smatch: 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=[] smatch: m[0]=[aB,def] suffix=[]
原子
原子 可以是下列之一:
原子 ::
- 模式字符
-
.
-
\
原子转义 - 字符类
-
(
析取)
-
(
?
:
析取)
其中 原子转义 ::
- 数字转义
- 字符转义
- 字符类转义
不同种类的原子求值方式不同。
子表达式
原子 (
析取 )
是有标记表达式:它执行析取 并存储析取 所消耗的输入子串于子匹配数组,下标对应在此点已遇到的整个正则表达式中,有标记子表达式的左开括号 (
次数。
除了在 std::match_results 中返回外,捕获的子匹配还可作为回溯引用(\1
、\2
……)访问,并在正则表达式中引用它们。注意 std::regex_replace 以同 String.prototype.replace (ECMA-262, part 15.5.4.11) 的方式,对于回溯引用以 $
代替 \
($1
、$2
……)。
原子 (
?
:
析取 )
(非标记子表达式)简单地求值析取 并不存储其结果于子匹配。这单纯是词法分组。
本节未完成 原因:暂无示例 |
回溯引用
数字转义 ::
- 十进制整数字面量 [前瞻 ∉ 十进制位]
如果 \
后随首位非 0
的十进制数 N
,那么认为该转义序列为回溯引用。通过在每个数位上调用 std::regex_traits::value(仅 C++) 并用底 10 算术组合及其结果获得 N
。如果 N
大于整个正则表达式中捕获括号的总数,那么就是错误。
回溯引用 \N
作为原子出现时,它匹配当前存储于子匹配数组中第 N 个元素的子串。
十进制转义 \0
不是回溯引用:它是表示空字符的字符转义。它不能为十进制数所后随。
如上,注意 std::regex_replace 对于回溯引用以 $
代替 \
($1
、$2
……)。
本节未完成 原因:暂无示例 |
单字符匹配
原子 .
匹配并消耗来自输入序列的任一字符,除了行终止符(U+000A
、U+000D
、U+2028
或 U+2029
)
原子 模式字符 ,其中模式字符 是任意源字符 ,除了字符 ^ $ \ . * + ? ( ) [ ] { } |
,匹配并消耗一个来自输入的字符,如果它等于此模式字符。
这个及所有其他单字符匹配定义如下:
每个由转义字符 \
后随字符转义 的原子,还有特殊十进制转义 \0
,匹配消耗一个来自输入的字符,如果它等于字符转义 所表示的字符。辨识下列字符转义序列:
字符转义 ::
- 控制转义
-
c
控制字母 - 十六进制转义序列
- Unicode 转义序列
- 恒等转义
此处控制转义 是下列五个字符之一:f n r t v
控制转义 | 编码单元 | 名称 |
---|---|---|
f
|
U+000C | 换页 |
n
|
U+000A | 换行 |
r
|
U+000D | 回车 |
t
|
U+0009 | 水平制表 |
v
|
U+000B | 垂直制表 |
控制字母 是任何小写或大写 ASCII 字符,而此字符转义所匹配字符的编码单元等于控制字母的编码单元的值除以 32 的余数,例如 \cD
和 \cd
都匹配编码单元 U+0004
(EOT),因为 'D' 是 U+0044
而 0x44 % 32 == 4
并且 'd' 是 U+0064
而 0x64 % 32 == 4
。
十六进制转义序列 是字母 x
后随准确二个十六进制位(其中十六进制位 是 0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F
之一)。此字符转义所匹配字符的编码单元等于二位十六进制数的数值。
Unicode 转义序列 是字母 u
后随准确四个十六进制位。此字符转义所匹配字符的编码单元等于此四位十六进制数的数值。如果该值不适于此 std::basic_regex 的 CharT,那么就会抛出 std::regex_error (仅 C++)。
恒等转义 能为任何非字母数字的字符:例如另一反斜杠。它照原样匹配字符。
#include <cstddef> #include <iostream> #include <regex> #include <string> void show_matches(const std::wstring& in, const std::wstring& re) { std::wsmatch m; std::regex_search(in, m, std::wregex(re)); if (!m.empty()) { std::wcout << L"input=[" << in << L"], regex=[" << re << L"]\n " L"prefix=[" << m.prefix() << L"]\n wsmatch: "; for (std::size_t n = 0; n < m.size(); ++n) std::wcout << L"m[" << n << L"]=[" << m[n] << L"] "; std::wcout << L"\n suffix=[" << m.suffix() << L"]\n"; } else std::wcout << L"input=[" << in << "], regex=[" << re << L"]: NO MATCH\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]+"); }
可能的输出:
input=[C++\], regex=[C\+\+\\] prefix=[] wsmatch: m[0]=[C++\] suffix=[] input=[ab?c], regex=[(\0{{!}}\u00ff)] prefix=[ab] wsmatch: m[0]=[?] m[1]=[?] suffix=[c] input=[?], regex=[[\u0000-\ufffe]+]: NO MATCH
字符类
原子能表示字符类,即它会匹配并消耗一个字符,若该字符属于预定义的字符组之一。
字符类能通过字符类转义引入:
原子 ::
-
\
字符类转义
或直接为
原子 ::
- 字符类
字符类转义是一些常用字符类的简洁写法,如下:
字符类转义 | 类名表达式(仅 C++) | 含义 |
---|---|---|
d
|
[[:digit:]]
|
数字 |
D
|
[^[:digit:]]
|
非数字 |
s
|
[[:space:]]
|
空白字符 |
S
|
[^[:space:]]
|
非空白字符 |
w
|
[_[:alnum:]]
|
字母数字字符及字符 _
|
W
|
[^_[:alnum:]]
|
异于字母数字或 _ 的字符
|
字符类 是方括号环绕的类范围 序列,可选地以取反运算符 ^
开始。如果它始于 ^
,那么此原子 匹配任何不在所有类范围 的并所表示的字符集合中的字符。否则,此原子 匹配任何在所有类范围 的并所表示的字符集合中的字符。
字符类 ::
-
[
[
前瞻 ∉ {^
}] 类范围]
-
[
^
类范围]
类范围 ::
- [空]
- 非空类范围
非空类范围 ::
- 类原子
- 类原子 非空无连字符类范围
- 类原子 - 类原子 类范围
如果非空类范围拥有形式 ClassAtom - ClassAtom
,那么它匹配来自定义如下的范围的任何字符:(仅 C++)
首个类原子 必须匹配单个校排元素 c1
,而第二个类原子 必须匹配单个校排元素 c2
。采用下列步骤,测试此范围是否匹配输入字符 c
:
transformed c1 <= transformed c && transformed c <= transformed c2
时匹配 c
按字面对待字符 -
,如果它是下列之一
- 类范围 的首或末字符
- 杠分隔范围规定的开始或结尾类原子
- 立即在杠分隔范围规定之后。
- 以反斜杠转义为字符转义
非空无连字符类范围 ::
- 类原子
- 无连字符类原子 非空无连字符类范围
- 无连字符类原子 - 类原子 类范围
类原子 ::
-
-
- 无连字符类原子
- 扩展类类原子 (仅 C++)
- 校排元素类原子 (仅 C++)
- 等价类原子(仅 C++)
无连字符类原子 ::
- 源字符(
\ ] -
除外) -
\
类转义
每个无连字符类原子 表示单个字符——原状的源字符 或转义如下的字符:
类转义 ::
- 数字转义
-
b
- 字符转义
- 字符类转义
特殊的类转义 \b
产生匹配编码单元 U+0008(退格)的字符集。在字符类 外,它是词边界断言。
字符类 内,\B
的使用和任何回溯引用(异于零的十进制转义)都是错误。
为将字符 -
和 ]
当做原子,一些情形中需要转义它们。其他拥有在字符类 外的特殊含义的字符,例如 *
或 ?
,不需要转义。
本节未完成 原因:暂无示例 |
基于 POSIX 的字符类
这些字符类是对 ECMAScript 语法的扩展,并等价于 POSIX 正则表达式中找到的字符类。
扩展类类原子 (仅 C++) ::
-
[:
类名:]
表示所有具名字符类类名 中的成员。仅若 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
)可为系统提供的本地环境提供,或实现为用户定义的扩展:
校排元素类原子 (仅 C++) ::
-
[.
类名.]
表示具名校排元素,它可表示单个字符,或在浸染的本地环境下作为单个单位校排的字符序列,例如 [.tilde.]
或捷克文中的 [.ch.]
。只有在 std::regex_traits::lookup_collatename 不是空字符串名称时才合法。
使用 std::regex_constants::collate 时,始终能以对照元素为范围的端点(例如匈牙利文中的 [[.dz.]-g]
)。
等价类原子 (仅 C++) ::
-
[=
类名=]
表示与具名校排元素相同的等价类的所有成员字符,即它的主校排键与校排元素类名 所拥有者相同的字符。只有在 std::regex_traits::lookup_collatename 对该名称返回非空字符串,且 std::regex_traits::transform_primary 对调用 std::regex_traits::lookup_collatename 的结果的返回值不是空字符串时名称才合法。
主排序键是忽略大小写、标音符或本地环境限定裁剪的键;因此例如 匹配任何这些字符之一:
a, À, Á, Â, Ã, Ä, Å, A, à, á, â, ã, ä
和 å
。
类名 (仅 C++) ::
- 类名字符
- 类名字符 类名
类名字符 (仅 C++) ::
- 源字符 (
. = :
除外)
本节未完成 原因:暂无示例 |