newLISP 的正则表达式接口是从 PCRE 标准函数库中获得的,继承了大部分的
功能,但不是完全相同。
在 newLISP 中表示一个正则表达式的时候,请使用大括号包围的方式,因为用双引号的方式,转义符号表示繁琐,行为也有一些怪异的地方。
(regex {\Q$\E} "$")
--> ("$" 0 1)
(regex "\Q$\E" "$")
--> nil
(regex "\\Q$\\E" "$")
--> ("$" 0 1)
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 语法)
] 结束一个字符类
下面讲讲每个元字符的用法:
在正则表达式中要表示元字符本身,就要用反斜杠来转义它。
(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).
> (find { \R } "\r\n\x0b\f\r\x85" 14)
nil
> (find { \R } "\r\n\x0b\f\r\x85 R" 14)
7
断言描述一个边界,可能是一行的开始或结束,也可能是一个单词的开始或结束。
\b 匹配一个单词的边界
\B 不是一个单词的边界
\A 匹配一个字符串的开始
\Z 匹配字符串的末尾,也匹配最后一个回车
\z 匹配字符串的末尾
这些用法不能用在一个字符类中。
一对方括号定义一个字符集。
如 [aeiou] 匹配所有的元音字符,而 [^aeiou] 则匹配元音字符之外的其他所有字符。
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” 或一个空字符串。
例如,如果字符串 “the red king” 被下面的模式匹配过:
the ((red|white) (king|queen))
那么捕获的子字符串有 “red king”, “red”, 和 “king”, 分别被保存在 $1, $2, $3 中。
如果分组括号第一个字符是问号 (?…), 那么这个分组不用于捕获,只用于分组:
the ((?:red|white) (king|queen))
这次捕获的子字符串只有 “white queen” 和 “queen”, 分别被保存在 $1 和 $2 中。
newLISP 最多可以捕获 65535 个分组.
重复定义了一个数量,可以跟在下面的字符后面:
一个字符的字面量 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”.
断言是在匹配过程中,对当前状态的一个测试。并不会让匹配指针发生变化。
\b \B \A \Z \z ^ $
都是一个断言描述符。
前瞻断言以 (?= 开始,用于匹配的模式,而 (?! 用于不匹配的模式:
\w+(?=;)
将匹配一个单词,跟着一个分号,但匹配结果并不包括这个分号:
foo(?!bar)
将匹配任何出现 “foo” 但后面没有跟着 “bar” 的情况.
向后看的语法是匹配 (?\<=
和 不匹配(?\<\!
:
> (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 支持反向引用:
> (regex {(\w+)\d+\1} "abc123abcd")
("abc123abc" 0 9 "abc" 0 3)
用户手册中的 regex replace find 等函数讲解了一些正则表达式应用的例子。
Last updated: 2014.06.05 Copyright 2014-2015 Michael.Song.