newLISP 正则表达式规范
newLISP 的正则表达式接口是从 PCRE 标准函数库中获得的,继承了大部分的 功能,但不是完全相同。
在 newLISP 中表示一个正则表达式的时候,请使用大括号包围的方式,因为用双引号的方式,转义符号表示繁琐,行为也有一些怪异的地方。
(regex {\Q$\E} "$") --> ("$" 0 1) (regex "\Q$\E" "$") --> nil (regex "\\Q$\\E" "$") --> ("$" 0 1)
支持字节和 utf-8 字符的处理
newLISP 支持基于字节和 utf-8 字符的正则表达式处理。只有支持 utf-8 的 newLISP 版本才能处理 utf-8 字符串。
当启动一个 REPL 时,出现包含 UTF-8 提示的版本才支持 utf-8 的字符处理:
newLISP v.10.6.0 32-bit on Win32 IPv4/6 UTF-8 libffi,options: newlisp -h > (regex {大象} "大象无形" 2048) ("大象" 0 6) > (regex {大象} "大象无形") ("大象" 0 6)
在 utf-8 版本的 newLISP 中,regex 的默认正则标识是 2048,而普通版本的默认正则标识是 0.
> (regex {[a-z]+} "thanks") ("thanks" 0 6) > (regex {[a-z]+} "thanks" 0) ("thanks" 0 6)
字符和元字符
一个正则表达式是一个模式,用于从左到右匹配一个字符串。正则表达式中的大部分字符代表自己,用于匹配字符串中相同的字符。作为一个简单的例子,这个匹配模式:
The quick brown fox
将会匹配和它一样的字符串片段:
> (regex {The quick brown fox} "get it: The quick brown fox") --> ("The quick brown fox" 8 19)
当不区分大小写选项开启时,匹配的内容将和大小写无关。
> (regex {The quick brown fox} "THE QUICK BROWN FOX" 1) --> ("THE QUICK BROWN FOX" 0 19)
正则表达式的威力来自可选的分支和重复的模式。描述它们需要用到元字符,它们将按照特殊的规则处理,已经不是原本的意思了。
有两套不同的元字符:在正则表达式的方括号 [...]
之间的部分,还有之外的部分。方括号之外的元字符有:
\ 转义字符,有几种用处 ^ 字符串开始的断言 (在多行模式下可以匹配行的开始) $ 字符串结束的断言 (在多行模式下可以匹配性的结束) . 匹配除回车之外的任何字符(默认) [ 开始一个字符类的定义 | 开始一个可选的分支 ( 开始一个子表达式 ) 子表达式结束 ? (? 扩展了 ( 的意思,也表示 0 或 1 的匹配数量 也可以表示最少的匹配 * 0 或更多的匹配数量 + 1 或更多的匹配数量,是贪婪匹配符 { 开始一个 "最少/最多" 的数量界定
在方括号内的模式描述叫 "字符类",字符类中的元字符有:
\ 字符转义 ^ 把类取反,如果在字符类定义的第一个字符 - 定义字符的范围连接符 [ POSIX 字符类定义开始符号 (只有跟随有效的 POSIX 语法) ] 结束一个字符类
下面讲讲每个元字符的用法:
反斜杠(BACKSLASH) \
在正则表达式中要表示元字符本身,就要用反斜杠来转义它。
(regex {\(\)\{\}\[\]\*\?\+\.\$\^\|} "(){}[]*?+$^|") --> ("(){}[]*?+.$^|" 0 13)
如果不想写这么多反斜杠,有个等价的表示方法:
(regex {\Q(){}[]*?+.$^|\E} "(){}[]*?+.$^|") --> ("(){}[]*?+.$^|" 0 13)
这种写法在字符类内部也可以:
(regex {[\Q(){}[]*?+.$^|\E]+} "(){}[]*?+.$^|") --> ("(){}[]*?+.$^|" 0 13)
不能打印的字符
newLISP 处理正则表达式,首先是按照字符串的规则处理其中的一些转义字符:
|-----------+---------------------------------------------| | 字符 | 描述 | +===========+=============================================+ | \" | 在字符串内部表示双引号 | |-----------+---------------------------------------------| | \n | 回车符 (ASCII 10) | |-----------+---------------------------------------------| | \r | 换行符 (ASCII 13) | |-----------+---------------------------------------------| | \b | 退格符 (ASCII 8) | |-----------+---------------------------------------------| | \t | TAB 符 (ASCII 9) | |-----------+---------------------------------------------| | \f | 换页符 (ASCII 12) | |-----------+---------------------------------------------| | \nnn | 三个数字的八进制的字符 ASCII 值 | | | (nnn 从 000 到 255) | |-----------+---------------------------------------------| | \xnn | 两个数字的十六进制字符 ASCII 值 | | | (xnn 从 x00 到 xff) | |-----------+---------------------------------------------| | \unnnn | 四个十六进制数字表示的 unicode 字符 | | | four nnnn hexadecimal digits. | | | newLISP 在 UTF8 版本中自动转化成 UTF8 字符 | |-----------+---------------------------------------------| | \\ | 反斜杠自己 (ASCII 92) | |-----------+---------------------------------------------|
通常的字符类
下面的字符类是一些通常使用到的字符类:
\d 任何十进制数字 \D 任何不是十进制的数字字符 \s 任何空白字符 \S 任何不适空白的字符 \w 任何单词的字符 \W 任何不适单词的字符
这些字符类有三对,分别代表另外一个范围的补集,如果一个字符匹配其中一个, 那么就补会再匹配另外一个了。
这些字符集在字符类内部和外部都是有效的。
\s 不会匹配 VT 符号(CODE 11). 这和 POSIX 中的 [:space:] 不同。\s 匹配的 字符有 HT(9),LF(10),FF(12),CR(13) 和 space(32).
newLISP 不支持 \R (换行符序列)
> (find { \R } "\r\n\x0b\f\r\x85" 14) nil > (find { \R } "\r\n\x0b\f\r\x85 R" 14) 7
简单断言(Simple assertions)
断言描述一个边界,可能是一行的开始或结束,也可能是一个单词的开始或结束。
\b 匹配一个单词的边界 \B 不是一个单词的边界 \A 匹配一个字符串的开始 \Z 匹配字符串的末尾,也匹配最后一个回车 \z 匹配字符串的末尾
这些用法不能用在一个字符类中。
方括号和字符集
一对方括号定义一个字符集。
如 [aeIoU] 匹配所有的元音字符,而 [^aeIoU] 则匹配元音字符之外的其他所有字符。
POSIX 字符类
newLISP 支持 POSIX 的字符类:
[01[:alpha:]%]
匹配 "0","1",任意字母,或 "%". 支持的有:
alnum letters and digits alpha letters ascii character codes 0 - 127 blank space or tab only cntrl control characters digit 十进制字符,和 \d 相同 graph printing characters,excluding space lower 小写字符 print 打印字符,包括空格 punct 打印字符,包括数字和字母 space 空白字符,[\s\x{0b}] upper 大写单词 word 单词字符,和 \w 相同 xdigit 十六进制数字
POSIX 字符集可以取反:
[12[:^digit:]]
在 UTF-8 模式下,POSIX 字符集并不会匹配超过 128 的字符。
分支符
垂直分割符定义了两个分支:
gilbert|sullivan
这个表达式既可以匹配 "gilbert" 也可以匹配 "sullivan". 分支可以有许多,分支的内容可以为空。
word1 | word2 | word3 | word4
内部标记
大小写不敏感 (?i:pattern) -- 大写字母和小写字母是一样的:
(find {(?i:pattern)} "PATTERN") --> 0
多行模式 (?m:pattern) -- ^ 只是匹配行首,而 $ 只匹配行尾
(find {(?m:^abc$)} "ddd\nabc\nfff" 1) --> 3
点扩展模式 (?s:pattern) - 点 (.) 可以匹配任意字符,包括回车符。
(find {(?s:\A.*?\z)} "hello\world" 1) --> 0
注释模式 (?x:pattern) -- 表达式中的空白将被默认删除,# 号以后到行尾的是注释
通常可以一起用:
(find {(?xms: hello # this is comment \s+ world) "hello world" 1) --> 1
表达式分组
分组表达式是用括号包围的部分,可以嵌套,它的作用有:
-
重新界定了分支的范围。例如:
cat(aract|erpillar|)
将匹配 "cat","cataract",或 "caterpillar". 如果没有分组标记,这个表达式 会匹配 "cataract","erpillar" 或一个空字符串。
- 它同时定义了一个可以捕获的分组表达式。意思是说,当进行匹配时,匹配到 分组表达式的字符串将被保存起来。在 newLISP 中,$1,$2,$3 等内部变量将 会保存分组表达式匹配到的字符串片段。
例如,如果字符串 "the red king" 被下面的模式匹配过:
the ((red|white) (king|queen))
那么捕获的子字符串有 "red king","red",和 "king",分别被保存在 $1,$3 中。
如果分组括号第一个字符是问号 (?...),那么这个分组不用于捕获,只用于分组:
the ((?:red|white) (king|queen))
这次捕获的子字符串只有 "white queen" 和 "queen",分别被保存在 $1 和 $2 中。 newLISP 最多可以捕获 65535 个分组.
命名捕获 -- newLISP 不支持
重复
重复定义了一个数量,可以跟在下面的字符后面:
一个字符的字面量 abc 点 . 字符类 反向引用 分组表达式 (除非是个断言)
通常的重复数量包括一个最小值和一个最大值,用大括号包围在一起,用逗号分隔。 最大的数值不能大于 65536,而且第一个数字必须比第二个要小:
z{2,4}
会匹配 "zz","zzz",和 "zzzz". 右大括号本身不是特殊字符,除非先看到左大括号。 如果第二个数字没有,但逗号有的话,那么就没有最大的限制。如果逗号和最大值都忽略了, 那么就是一个固定的数量限制。
[aeIoU]{3,}
这个模式匹配至少 3 个字母,但最多可以匹配许多许多,而:
\d{8}
只匹配正好 8 个数字。缺少最小值的数量限制标记是不合法的。就好象:
\w{,10}
这只是能匹配自身的普通字符而已。
为方便起见,有三个数量限定符设置了简写形式:
* 等价于 {0,} + 等价于 {1,} ? 等价于 {0,1}
通常,数量限定符都是 "贪婪的",意思是说,他们会尽量匹配最多的字符,直到 再也匹配不到东西。
如果,一个数量限制标记后面跟一个问号,那么这个表达式就会变得不再 "贪婪“, 它将尽量捕获尽可能少的字符,只要满足条件就可以了。
/\*.*?\*/
这是 C 语言注释的模式。它通常是可以正常工作的。
反向引用
分组捕获的结果,不但在系统变量中保存,在正则表达式中同样可以调用:
(sens|respons)e and \1ibility
将会匹配 "sense and sensibility" 和 "response and responsibility",而不是 "sense and responsibility".
断言 (ASSERTIONS)
断言是在匹配过程中,对当前状态的一个测试。并不会让匹配指针发生变化。
\b \B \A \Z \z ^ $
都是一个断言描述符。
前瞻断言 Lookahead assertions
前瞻断言以 (?= 开始,用于匹配的模式,而 (?! 用于不匹配的模式:
\w+(?=;)
将匹配一个单词,跟着一个分号,但匹配结果并不包括这个分号:
foo(?!bar)
将匹配任何出现 "foo" 但后面没有跟着 "bar" 的情况.
顾后断言 lookbehind
向后看的语法是匹配 (?\<=
和 不匹配(?\<\!
:
> (regex {(?<=[a-z]+)\d+} "..123ab456") ("456" 7 3) ;; 前面匹配的模式不能有不定的数量匹配符号 >(regex {(?<=[a-z]+)\d+} "..123ab456") ERR: regular expression in function regex : "offset 10 lookbehind assertion is not fixed length" > (regex {(?<=[a-z][a-z])\d+} "..123ab456") ("456" 7 3) > (regex {(?<![a-z])\d+} "..123ab456") ("123" 2 3) > (regex {(?<![a-b]|[c-d])\d+} "..123ab456") ("123" 2 3) > (regex {(?<![a-b]|[c-d][e-f])\d+} "..123ab456") ("123" 2 3) > (regex {ab(?=[0-9])} "abcdab12") ("ab" 4 2)
注释
newLISP 支持在正则表达式内插入注释,这让表达式更具可读性:
> (regex {(?#this is comment)ab} "ab") ("ab" 0 2)
newLISP 支持递归调用
捕获值作为函数
newLISP 支持反向引用:
> (regex {(\w+)\d+\1} "abc123abcd") ("abc123abc" 0 9 "abc" 0 3)
调用外部函数 -- newLISP 不支持
参考资料
用户手册中的 regex replace find 等函数讲解了一些正则表达式应用的例子。
Last updated: 2014.06.05 Copyright 2014-2015 Michael.Song.