pregexp: 用于 Scheme 和 Common Lisp 可移植正则表达式库

pregexp: 用于 Scheme 和 Common Lisp 可移植正则表达式库

pregexp 是一个可移植的正则表达式库,可在任何符合 R4RS、R5RS 或 R6RS [2] 的 Scheme 中运行。 它提供了模仿 Perl 的 [1,3]的正则表达式,并包括诸如数字和非贪心量词、捕获和非捕获的分组、POSIX 字符类、选择性大小写和空格不敏感、反向引用、交替、回溯修剪等强大的指令。 除了所有 regexp 用户都熟悉的更基本的指令外,还有正向和负向的前瞻和回顾。

要使用 pregexp,只需要将 pregexp.scm 加载到你的 Scheme 中。(如果您使用的方言允许,您也可以将 pregexp 作为一个模块安装——请参阅发行版中的文件install。)

pregexp.lisp 是该库的 Common Lisp 版本。要使用它,请将其加载到 Common Lisp 中。(本手册中的描述和示例使用 Scheme 语法,但将其翻译为CL语法很容易。)

1. 简介

正则表达式是一个模式字符串,正则表达式匹配器会尝试与另一个字符串(的一部分)进行匹配,被匹配的字符串被视为原始文本,而不是一个模式。

正则表达式中的大多数字符会匹配原始文本中出现的自己。因此, "abc"会匹配包含a, b, c三个连续字符的字符串。

在正则表达式模式中,一些字符被视为“元字符”,一些字符序列被视为“元序列”,也就是说,它表示的并不是该字符本身。例如,在正则表达式 "a.c" 中,字符ac表示的是字符 ac本身,然而.可以匹配任意的字符(除了换行符)。所以, "a.c"可以匹配以a开头,以c结尾的任意三个字符,比如: "abc", "aac", "afc", "a*c"...

如果我们需要精确匹配.本身,就需要使用转义字符,就是在前面加上一个反斜杠 \,反斜杠也是一个元字符,但是它不匹配任何字符,而是将紧跟着它的元字符变成一个普通字符。比如: "a\\.c"可以匹配"a.c", 使用双斜杠的原因是,在 Scheme 的字符串中,反斜杠本身就是转义字符,要在Scheme字符串中包含一个反斜杠,就需要双反斜杠。就像在 C 中一样。另一个例子是 \t,它以一种可读的方式来表示 tab 字符。

我们将字符串表示的正则表达式称为 U-regexp ,U 可以被解释为 Unix-style 或者 universal 。因为这种正则表达式的表示法被普遍接受。我们的实现使用一种树形的中间表示法,称之为 S-regexp ,S 可以被理解为 Scheme, symbolic 或者 S-expression. S-regexp 更冗长,并且不易读,不易理解,但是便于 Scheme 的递归过程处理。

2. 正则表达式过程

pregexp.scm 提供了如下几个过程: pregexp , pregexp-match-positions , pregexp-match, pregexp-split, pregexp-replace, pregexp-replace*, pregexp-quote. 由 pregexp.scm 引入的所有过程都有 'pregexp' 前缀,所以它们不太可能和 Scheme 中的其他名称冲突,包括由实现本身提供的正则表达式过程的名称。

2.1 pregexp

pregexp 接受一个字符串表示的正则表达式模式(U-regexp), 返回一个 S-regexp

(pregexp "c.r")
=> (:sub (:or (:seq #\c :any #\r)))

2.2 pregexp-match-positions

pregexp-match-positions 过程接受一个正则表达式和一个原始文本字符串,如果匹配成功,返回一个 match,否则返回 #f

正则表达式可以是 UNIX 风格的正则字符串,或者是树形的 S-regexp 。在内部, pregexp-match-positions 首先将字符串表示的正则表达式编译成 S-regexp ,然后再进行匹配。如果你发现一个正则表达式有可能会被多次用到,那么明智的做法是用 pregexp 过程将它显式地转换成 S-regexp ,并且保存在一个临时变量中,这样可以节省重新编译的时间。

pregexp-match-positions 返回 #f(如果匹配失败) 或者一个点对列表(如果匹配成功).

(pregexp-match-positions "brain" "bird")
=> #f

(pregexp-match-positions "needle" "hay needle stack")
=> ((4 . 10))

在第二个例子里,整数 4 和 10 标志着被匹配的子串,4 代表子串的索引开始,10 代表索引结束(10 索引处的字符并不包括在内,这与普遍意义上的字符串索引是一致的)。

(substring "hay needle stack" 4 10)
=> "needle"

这里, pregexp-match-positions 返回的列表仅包含一个索引对,该索引对表示匹配的子串在整个字符串中的位置。当我们稍后讨论子模式时,我们将看到单个匹配操作如何产生子匹配列表。

pegexp-match-positions 接受可选的第三和第四个参数,指定将要被匹配的子串。

(pregexp-match-positions "needle"
  "his hay needle stack -- my hay needle stack -- her hay needle stack"
  24 43)
=> ((31 . 37))

注意,返回的索引依然是相对于整个字符串来计算的。

2.3 pregexp-match

pregexp-match 的调用类似于 pregexp-match-positions ,但是它返回的是匹配的子串,而不是索引位置。

(pregexp-match "brain" "bird")
=> #f

(pregexp-match "needle" "hay needle stack")
=> ("needle")

pregexp-match 同样接受可选的第三和第四个参数。

2.4 pregexp-split

pregexp-split 过程接受两个参数,一个正则表达式以及一个文本字符串,返回文本字符串的子串构成的列表,由被匹配的子串充当分隔。

(pregexp-split ":" "/bin:/usr/bin:/usr/bin/X11:/usr/local/bin")
=> ("/bin" "/usr/bin" "/usr/bin/X11" "/usr/local/bin")

(pregexp-split " " "pea soup")
=> ("pea" "soup")

如果第一个参数指定为空字符串,则返回由单个字符组成的列表:

(pregexp-split "" "smithereens")
=> ("s" "m" "i" "t" "h" "e" "r" "e" "e" "n" "s")

要在分隔符中表示超过一个的空格,需要使用正则表达式 " +", 而不是 " *"

(pregexp-split " +" "split pea     soup")
=> ("split" "pea" "soup")

(pregexp-split " *" "split pea     soup")
=> ("s" "p" "l" "i" "t" "p" "e" "a" "s" "o" "u" "p")

2.5 pregexp-replace

regexp-replace 过程将匹配的子串替换为另一个字符串

(pregexp-replace "te" "liberte" "ty")
=> "liberty"

如果没有可匹配的子串,则原样返回文本字符串(eq? 意义上的相等,即同一个对象)。

2.6 pregexp-replace*

pregexp-replace* 替换所有被匹配的子串:

(pregexp-replace* "te" "liberte egalite fraternite" "ty")
=> "liberty egality fratyrnity"

pregexp-replace 一样,如果没有匹配,则原样返回原来的文本字符串

2.7 pregexp-quote

pregexp-quote 接受任意一个字符串,返回一个可以精确地表示它的 U-regexp (字符串)。特别是,在输入字符串中可以用作正则表达式元字符的特殊字符会被反斜杠转义,以便它们安全地只匹配自己。

(pregexp-quote "cons")
=> "cons"

(pregexp-quote "list?")
=> "list\\?"

当从一个混合了正则表达式字符串以及逐字的字符串构建复合的正则表达式时 pregexp-quote 相当有用。(为什么这么绕?)

3 正则表达式模式语言

这里完整地描述 pregexp 使用的正则表达式模式语言

3.1 基本的断言

^$ 分别表示字符串的开头和结尾。它们确保靠近它们的正则表达式匹配一个字符串的开头或结尾。例如:

(pregexp-match-positions "^contact" "first contact")
=> #f

匹配失败,因为 'contact' 并没有出现在文本字符串的开头。

(pregexp-match-positions "laugh$" "laugh laugh laugh laugh")
=> ((18 . 23))

该正则表达式匹配了最后一个 'laugh'。

元序列 \b 断言存在单词边界。

(pregexp-match-positions "yack\\b" "yackety yack")
=> ((8 . 12))

'yackety' 里的 'yack' 后边没有存在单词边界,所以它没有被匹配。第二个 'yack' 则匹配成功。

元序列 \B 的意思正好相反。它断言单词边界不存在。

(pregexp-match-positions "an\\B" "an analysis")
=> ((3 . 5))

多说一句,第一个出现的 'an',后面是空格,所以没有被匹配;而 'analysis' 开头的 'an',后面紧挨着的是'alysis',没有间隔存在,所以被匹配。

3.2 字符和字符类

通常,正则表达式中的字符与文本字符串中相同的字符相匹配。有时,使用正则表达式来引用单个字符是必要的或者方便的。因此,元序列 \n, \r, \t 以及 \. 分别匹配 newline, return, tab 以及.

元字符 . 匹配除了 \n 之外的任意字符。

(pregexp-match "p.t" "pet")
=> ("pet")

它同样匹配 'pat', 'pit', 'pot', 'put', 以及 'p8t',但是不能匹配 'pfffft'.

字符类匹配一组字符集合中的任意一个字符。典型的字符类是由方括号括起来的一组字符 [...], 它匹配方括号中包含的非空字符序列中的任意一个字符。因此,"p[aeiou]t" 可以匹配 'pat', 'pet', 'pit', 'pot', 'put' 等等。

在方括号中,两个字符之间的连号 - 指定 ASCII 码表里,两个字符之间的一个范围。例如,"ta[b-dgn-p]" 匹配 'tab', 'tac', 'tad', 'tag', 以及 'tan', 'tao', 'tap'。

左括号后面的符号 ^ 反转由剩下的内容指定的集合,即它指定除方括号中标识的字符之外的字符集合。例如,"do[^g]" 匹配由 'do' 开头的所有三个字符,除了 'dog'。

要注意,方括号里的 ^ 和它在方括号外的意思完全不一样。大多数其他元字符(. * + ? 等)到了方括号中就不再是元字符了,虽然为了 peace of mind 仍然可以转义它们。- 只有在方括号内才是一个元字符,当然它不能是方括号里的第一个,也不能是最后一个字符。

方括号字符类不能包含其他带方括号的字符类(尽管它们能包含某些其他类型的字符类——下面将会看到)。因此,在一个带方括号的字符类中,单独的左括号不再是一个元字符,它可以代表它自己。例如:"[a[b]" 匹配 'a', '[', 能及 'b'。

此外,由于方括号字符类不能为空,所以紧接在开头的左括号之后的右括号也不被视为元字符。例如:"[]ab]" 匹配 ']', 'a' 和 'b'。

3.2.1 常用的字符类

一些标准字符类可以方便地表示为元序列,而不是显式的方括号表达式。\d 匹配一个数字[0-9]\s 匹配一个空白字符;\w 匹配可能是“单词”的一部分的字符。(遵循正则表达式的惯例,我们认定“单词”字符是 [A-Za-z0-9_] , 也就是能用做 C 语言标识符的字母、数字和下划线), 虽然这与一个 Scheme 程序员所认为的单词的定义相比可能太过严格(在 Lisp 和 Scheme 里,标识符所能使用的字符太自由了)。

这些元序列的大写版本表示相反的意思,\D 匹配非数字字符,\S 匹配非空白字符,\W 匹配非单词字符。

将这些元序列放置在 Scheme 字符串中时,请记住要写成双反斜械:

(pregexp-match "\\d\\d"
  "0 dear, 1 have 2 read catch 22 before 9")
=> ("22")

这些字符类可以使用在一个方括号表达式中,例如:"[a-z\\d]"匹配一个小写字母或者一个数字。

3.2.2 POSIX 字符类

POSIX 字符类是一种格式为 [: ... :] 的特殊元序列,只能在方括号表达式中使用。支持的 POSIX 字符类包括:

[:alnum:]       ;; 字母和数字
[:alpha:]       ;; 字母
[:algor:]       ;; 字母 'c', 'h', 'a' 和 'd'
[:ascii:]       ;; 7位 ASCII 字符
[:blank:]       ;; 空白符,即 空格 和 制表符(不包括回车?)
[:cntrl:]       ;; 控制字符,即 ASCII 码表中小于 32 的那些
[:digit:]       ;; 数字,与 '\d' 相同
[:graph:]       ;; ???
[:lower:]       ;; 小写字母
[:print:]       ;; ???
[:space:]       ;; 空白符,与 '\s' 相同
[:upper:]       ;; 大写字母
[:word:]        ;; 字母,数字以及下划线,与 \w 相同
[:xdigit:]      ;; 十六进制数字

例如,正则表达式"[[:alpha:]_]" 匹配一个字母或下划线

(pregexp-match "[[:alpha:]_]" "--x--")
=> ("x")

(pregexp-match "[[:alpha:]_]" "--_--")
=> ("_")

(pregexp-match "[[:alpha:]_]" "--:--")
=> #f

POSIX 类只有在额外的方括号中才有效,当它不在方括号表达式中时,例如 "[:alpha:]",不会被认为是字母类。按照以前的原则,它只能匹配 ':', 'a', 'l', 'p', 'h' 这几个字符。

(pregexp-match "[:alpha:]" "--a--")
=> ("a")

(pregexp-match "[:alpha:]" "--_--")
=> #f

通过在 [: 后面紧跟着插入一个 ^, 你得到 POSIX 字符类的反转。因此,[:^alpha:] 表示除了字母以外的所有字符。

3.3 量词

量词 *, + 以及 ? 分别匹配前面的子模式: 0或0个以上,1个或1个以上,0个或1个实例。

(pregexp-match-positions "c[ad]*r" "cadaddadddr")
=> ((0 . 11))
(pregexp-match-positions "c[ad]*r" "cr")
=> ((0 . 2))

(pregexp-match-positions "c[ad]+r" "cadaddadddr")
=> ((0 . 11))
(pregexp-match-positions "c[ad]+r" "cr")
=> #f

(pregexp-match-positions "c[ad]?r" "cadaddadddr")
=> #f
(pregexp-match-positions "c[ad]?r" "cr")
=> ((0 . 2))
(pregexp-match-positions "c[ad]?r" "car")
=> ((0 . 3))

3.3.1 数字量词

你可以使用大括号来指定比使用 * + ? 更精细的数量。

量词 {m} 精确过匹配前面的子模式 m 个实例, m 必须是非负的整数。

量词 {m,n}; 匹配最少 m 个,最多 n 个实例。m 和 n 必须是非负的整数,并且 m <= n。两者都可以省略,在这种情况下,m 默认为 0, 而 n 表示无限大。

很明显,+? 分别是 {1,}{0,1} 的缩写,*{,} 的缩写,并且与 {0,} 等价。

(pregexp-match "[aeiou]{3}" "vacuous")
=> ("uou")

(pregexp-match "[aeiou]{3}" "evolve")
=> #f

(pregexp-match "[aeiou]{2,3}" "evolve")
=> #f

(pregexp-match "[aeiou]{2,3}" "zeugma")
=> ("eu")

3.3.2 非贪心量词

上面所描述的量词都是 贪心 的,即,它们匹配所能匹配的最大数量的实例。

(pregexp-match "<.*>" "<tag1> <tag2> <tag3>")
=> ("<tag1> <tag2> <tag3>")

要将这些量词变成 非贪心 的,在后面附加一个问号 ? 即可。非贪心量词只匹配最小数量的实例。

(pregexp-match "<.*?>" "<tag1> <tag2> <tag3>")
=> ("<tag1>")

非贪心量词分别是:*?, +?, ??, {m}?, {m,n}?。要注意元字符 ? 的两种不同的用法。

3.4 分组

分组,就是用圆括号包围起来的表达式(...), 将圆括号中的子模式识别为一个单独的正则表达式实体。它使得匹配器捕获子模式,并且将文本字符串中匹配子模式的部分附加到整体匹配当中。所谓整体匹配,就是假装所有的圆括号都不存在(在子模式后面有量词的情况下,这种表述不正确),进行匹配。整体匹配后再将每一对圆括号都视为一个单独的正则表达式,分别进行匹配,最后匹配的结果会附加到整体匹配的结果里面去。

(pregexp-match "([a-z]+) ([0-9]+), ([0-9]+)" "jan 1, 1970")
=> ("jan 1, 1970" "jan" "1" "1970")

分组导致接下来的量词将整个封闭起来的子模式视为一个独立的实体。

(pregexp-match "(poo )*" "poo poo platter")
=> ("poo poo " "poo ")

子匹配所返回的数量总是等于正则表达式中指定的子模式的数量。哪怕一个子模式匹配多个子串,或者是一个也不匹配。

(pregexp-match "([a-z ]+;)*" "lather; rinse; repeat;")
=> ("lather; rinse; repeat;" " repeat;")

在这里,被量词修饰的子模式匹配了三次,但是最后它只返回了一次。

被量词修饰的子模式也有可能不匹配,即便总体是是匹配成功的。在这种情况下,失败的子匹配用 #f 表示。

(define date-re
  ;match `month year' or `month day, year'.
  ;subpattern matches day, if present
  (pregexp "([a-z]+) +([0-9]+,)? *([0-9]+)"))

(pregexp-match date-re "jan 1, 1970")
=> ("jan 1, 1970" "jan" "1," "1970")

(pregexp-match date-re "jan 1970")
=> ("jan 1970" "jan" #f "1970")

3.4.1 反向引用

子匹配可以在过程 pregexp-replacepregexp-replace* 的插入字符串参数中使用。 插入字符串可以使用\n作为反向引用来引用第n个子匹配,即匹配第n个子模式的子字符串。\0表示整个匹配,也可以指定为\&

(pregexp-replace "_(.+?)_"
  "the _nina_, the _pinta_, and the _santa maria_"
  "*\\1*")
=> "the *nina*, the _pinta_, and the _santa maria_"

(pregexp-replace* "_(.+?)_"
  "the _nina_, the _pinta_, and the _santa maria_"
  "*\\1*")
=> "the *nina*, the *pinta*, and the *santa maria*"

;recall: \S stands for non-whitespace character

(pregexp-replace "(\\S+) (\\S+) (\\S+)"
  "eat to live"
  "\\3 \\2 \\1")
=> "live to eat"

在插入字符串中使用 \\ 指定一个字面的反斜杠。另外,\$ 代表空字符串,可以用于将反引用 \n 与紧领的数字分隔开。

反向引用也可以在正则表达式模式中使用,以引用模式中已经匹配的子模式。 \n 代表第 n 个子匹配的精确重复。

(pregexp-match "([a-z]+) and \\1"
  "billions and billions")
=> ("billions and billions" "billions")

注意,反向引用不仅仅是前面的子模式的重复。相反,它是已经由子模式匹配的特定子串的重复。

在上面的例子中,反向引用只能匹配 'billions', 它不能匹配 'millions', 即使它回溯到子模式 ([a-z]+) —— 这样做没问题。

(pregexp-match "([a-z]+) and \\1"
  "billions and millions")
=> #f

下面是对单词的修正:

(pregexp-replace* "(\\S+) \\1"
  "now is the the time for all good men to to come to the aid of of the party"
  "\\1")
=> "now is the time for all good men to come to the aid of the party"

以下标记数字字符串中所有立即重复的模式:

(pregexp-replace* "(\\d+)\\1"
  "123340983242432420980980234"
  "{\\1,\\1}")
=> "12{3,3}40983{24,24}3242{098,098}0234"

3.4.2 非捕获分组

有时会需要指定一个分组(通常用于量化),但不能触发子匹配信息的捕获。这样的分组称为非捕获分组。在这种情况下,使用 (?: 而不是 ( 作为分组的开始。在下面的例子中,非捕获分组消除了给定路径名的“目录”部分,而捕获分组标识了文件名。

(pregexp-match "^(?:[a-z]*/)*([a-z]+)$"
  "/usr/local/bin/mzscheme")
=> ("/usr/local/bin/mzscheme" "mzscheme")

3.4.3 Cloisters

在一个非捕获分组的 ?: 之间的位置称为 cloister . 你可以在那里添加修饰符,这将产生一个被特殊处理的子模式。修饰符 i 使子模式匹配大小写不敏感:

(pregexp-match "(?i:hearth)" "HeartH")
=> ("HeartH")

修饰符 x 使子模式匹配对空白符不敏感,即,子模式中的空格和注释将被忽略。注释通常以分号开头,一直延续到行末。如果你需要在对空白不敏感的子模式中包含一个字面意义上的空格或者分号,可以用反斜杠来转义它们。

(pregexp-match "(?x: a   lot)" "alot")
=> ("alot")

(pregexp-match "(?x: a  \\  lot)" "a lot")
=> ("a lot")

(pregexp-match "(?x:
   a \\ man  \\; \\   ; ignore
   a \\ plan \\; \\   ; me
   a \\ canal         ; completely
   )"
 "a man; a plan; a canal")
=> ("a man; a plan; a canal")

全局变量 *pregexp-comment-char* 包含了注释字符 (#\;) ,要使用 Perl 风格的注释符,可以:

(set! *pregexp-comment-char* #\#)

你可以在 cloister 里添加更多的修饰符

(pregexp-match "(?ix:
   a \\ man  \\; \\   ; ignore
   a \\ plan \\; \\   ; me
   a \\ canal         ; completely
   )"
 "A Man; a Plan; a Canal")
=> ("A Man; a Plan; a Canal")

在一个修饰符前添加减号- 会反转其含义。因此,你可以使用 -i 以及 -x 来推翻由封闭分组引起的不敏感性。

(pregexp-match "(?i:the (?-i:TeX)book)"
  "The TeXbook")
=> ("The TeXbook")

这个正则表达式可以同时匹配大写和小写'the'和'book',但是 TeX 的大小写必须精确匹配。

3.5 逻辑或

您可以通过分隔符 | 来指定子模式的列表。 | 在分组中分隔子模式(如果没有封闭的括号,则作用于整个模式字符串范围)。

(pregexp-match "f(ee|i|o|um)" "a small, final fee")
=> ("fi" "i")

(pregexp-replace* "([yi])s(e[sdr]?|ing|ation)"
   "it is energising to analyse an organisation
   pulsing with noisy organisms"
   "\\1z\\2")
=> "it is energizing to analyze an organization
   pulsing with noisy organisms"

再次提醒,如果你希望仅使用分组来指定备用子模式的列表,但是不希望子匹配,请使用(?:而不是(

(pregexp-match "f(?:ee|i|o|um)" "fun for all")
=> ("fo")

关于逻辑或,最重要的一点是,最左边的子模式总是被最先选中,而不管它的长度。因此,如果一个子模式是它后面的子模式的前缀,则后者可能没有机会被匹配到。

(pregexp-match "call|call-with-current-continuation"
  "call-with-current-continuation")
=> ("call")

所以,为了让较长的子模式有被匹配的机会,请将较长的子模式放在较短的子模式前面。

(pregexp-match "call-with-current-continuation|call"
  "call-with-current-continuation")
=> ("call-with-current-continuation")

在任何情况下,整个正则表达式的整体匹配总是优先于整体不匹配。 在下文中,较长的子模式仍然在竞争中获胜,因为它首选的较短前缀无法产生整体匹配。

(pregexp-match "(?:call|call-with-current-continuation) constrained"
  "call-with-current-continuation constrained")
=> ("call-with-current-continuation constrained")

3.6 回溯

我们已经看到,贪心量词总是匹配最大次数,但是最重要的优先级是整体匹配成功。思考下面的例子

(pregexp-match "a*a" "aaaa")

该正则表达式由两个子正则表达式组成,a 后面跟着 *a 。就算 * 是一个贪心量词,
*a 也不被允许匹配 "aaaa" 中所有的 4 个 a , 它只能匹配最开始的 3 个 a,留下最后一个 a 用于第二个子正则表达式。这样将确保整个正则表达式匹配成功。

正则表达式匹配器通过一个称为回溯的过程来做到这一点。匹配器暂时允许贪心量词匹配所有的 4 个 a ,但是当它意识到这样会导致整体匹配失败时,它会回溯到更少的贪心匹配 3 个 a,甚至如果这样还会失败,比如下面的调用:

(pregexp-match "a*aa" "aaaa")

匹配器还会进一步回溯,只有当所有可能的回溯都尝试过才会发生整体匹配失败。

回溯并不限于贪心量词,非贪心量词匹配尽可能少的实例,并逐渐回溯到越来越多的实例,以实现整体匹配成功。在 alternation 的匹配中也会进行回溯,当左边的 alternation 会导致整体匹配失败时,会尝试右边的 alternation 。

3.6.1 禁止回溯

有时禁止回溯会更有效。例如,我们可能希望做出选择,或者我们知道尝试子模式是徒劳的。非回溯式正则表达式包含在 (?>...). 之间

(pregexp-match "(?>a+)." "aaaa")
=> #f

在这个调用里,子表达式 ?>a+ 贪婪地匹配所有 4 个 a,并且拒绝回溯的机会。所以整体匹配失败。因此这个正则表达式的效果是匹配一个或多个 a,后面跟一个肯定不是 a 的东西。

3.7 前瞻

您可以在模式中使用向前或向后的断言,以确保子模式发生或不发生。 这些“环顾四周”断言是通过将检查的子模式放在一个前导字符为?=(正向前瞻)或?!(反向前瞻)或?<=(正向回顾)或?<!(反向回顾)的组中来指定。请注意,断言中的子模式不会在最终结果中生成匹配项。 它只是允许或禁止匹配的剩余部分。

3.7.1 前瞻

正向前瞻 (?=) 确保其子模式可以匹配。

(pregexp-match-positions "grey(?=hound)"
  "i left my grey socks at the greyhound")
=> ((28 . 32))

正则表达式 "grey(?=hound)" 匹配 grey, 前提是它后面跟着hound。因此,文本字符串中的第一个grey并不匹配。

反向前瞻 (?!) 向前窥视,以确保其子模式不可能匹配。

(pregexp-match-positions "grey(?!hound)"
  "the gray greyhound ate the grey socks")
=> ((27 . 31))

正则表达式 "grey(?!hound)" 匹配 grey, 但前提是它后面没有跟着 hound。 因此socks之前的grey是匹配的。

3.7.2 回顾

正向回顾 (?<=) 检查其子模式是否可以立即匹配到文本字符串中当前位置的左侧。

(pregexp-match-positions "(?<=grey)hound"
  "the hound in the picture is not a greyhound")
=> ((38 . 43))

正则表达式 (?<=grey)hound 只匹配跟在 grey 后面的 hound

反向回顾 (?<!) 检查它的子模式不可能立即匹配到左边。

(pregexp-match-positions "(?<!grey)hound"
  "the greyhound in the picture is not a hound")
=> ((38 . 43))

正则表达式 (?<!grey)hound 只匹配没有跟在grey后面的hound

不要混淆的情况下,前瞻和回顾会很方便。

posted @ 2017-01-08 22:44  fmcdr  阅读(898)  评论(0编辑  收藏  举报