newLISP 正则表达式简介

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

表达式分组

分组表达式是用括号包围的部分,可以嵌套,它的作用有:

  1. 重新界定了分支的范围。例如:

    cat(aract|erpillar|)

将匹配 “cat”, “cataract”, 或 “caterpillar”. 如果没有分组标记,这个表达式
会匹配 “cataract”, “erpillar” 或一个空字符串。

  1. 它同时定义了一个可以捕获的分组表达式。意思是说,当进行匹配时,匹配到
    分组表达式的字符串将被保存起来。在 newLISP 中,$1, $2, $3 等内部变量将
    会保存分组表达式匹配到的字符串片段。

例如,如果字符串 “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 个分组.

命名捕获 – 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.

你可能感兴趣的:(newLISP 正则表达式简介)