共计 2870 个字符,预计需要花费 8 分钟才能阅读完成。
上一章:迭代
在第二章「文本解析」所实现的解析器程序里,为了判断一行文本是否以 \`\`\`
结尾,我定义了一个函数:
(defun text-match (source target)
(setq n (length target))
(if (< (length source) n)
nil
(string= (substring source 0 n) target)))
事实上,Elisp 提供了更弱小的文本匹配函数。如何的弱小呢?弱小到了反对正则表达式匹配。
正则表达式,就像现代官府捉拿江洋大盗时在城门边上张贴的通缉告示上的罪犯画像。罪犯的长相越有特点,他的画像便越有用途。我还感觉古代的机器学习程序在辨认照片里的人脸,其原理也像是在城门边上张贴通缉告示。
如何给一段文本画像呢?具体而言,如何给以 \`\`\`
作为结尾的文本画像呢?很简略,只有像上面这样画
^```
^
的意思是「结尾」,前面紧跟着 \`\`\`
,就示意结尾是 \`\`\`
。
Elisp 的 string-match
函数能够用正则表达式形成的字符串对象去匹配另一个字符串对象,例如:
(string-match "^```" "```lisp")
留神,为了便于讲述,从当初开始,诸如字符串对象(或字符串类型的实例),列表对象(或列表类型的实例),若没有非凡申明,通通简称为字符串、列表。应该不会导致误会。
上述示例中,因为字符串 "\`\`\`lisp"
是以 \`\`\`
结尾的,所以 string-match
的求值后果不是 nil
,否则是 nil
。对于 Elisp 解释器而言,非 nil
即为真,亦即若一个值即不是 nil
,也不是 '()
,那么无论它是什么,Elisp 都会将其等价于 t
。还记得吗,之前说过的,nil
与 '()
等价。要牢记住这些。事实上,上例的求值后果是 0,但 0 即不是 nil
也不是 '()
。
为什么上例的求值后果是 0 呢?因为 string-match
在字符串的结尾就找到了与正则表达式相匹配的局部。字符串的结尾,亦即字符串第一个字符的索引(或下标),它的值是 0。再看一个例子:
(setq r "```")
(setq x "foo```bar")
(string-match r x)
此时,string-match
是判断字符串 x
中是否存在与正则表达式 r
相匹配的文本,求值后果是匹配的文本的第一个字符的索引。因为在 x
里,\`\`\`
的首字符的索引是 3,所以上例里 string-match
的求值后果就是 3。这个求值后果的含意是,合乎正则表达式 r
的的文本在 x
的第 4 个字符地位开始呈现。
上面的这个例子,
(setq r "```$")
(setq x "foo```")
(string-match r x)
能够判断 x
是否以 \`\`\`
结尾。在正则表达式里,$
示意文本的结尾。
猜一下,^\`\`\`$
是什么意思?猜中了,尽管没有处分,但能够确定本人并不笨。
当初,第二章的解析器程序里无关文本匹配的性能,便能够应用 string-match
代替了。至此,与该解析器无关的常识,均已遍及。它所解决的问题,当初已不是问题了。我须要发现新的问题。
新的问题还是在 foo.md 文件里。上面仅给出它的局部内容:
# Hello world!
上面是 C 语言的 Hello world 程序源文件 hello.c 的内容:```
#include <stdio.h>
... ... ...
```
... ... ...
其中,# Hello world!
是文档大节的题目。应用正则表达式 ^#
能够匹配它,然而抄录环境里也有以 #
结尾的文本行。当初是不是有一些明确了,为什么从第二章到当初,我对 \`\`\`
结尾的文本行如此有执念了吧?只有先辨认出抄录环境,将它们疏忽,方有足够的可能匹配文档大节的题目。至于如何疏忽抄录环境里的文本,当初且放下。只须要记得,当初有了一个新的问题,而且接下来我也不晓得还须要用几章能彻底解决它。
在疏忽抄录环境的前提下,应用 ^#
能够匹配文档大节题目,然而它太毛糙了。因为,文档大节题目的实在样子能够是以下几种
# 题目
# 题目
# 题目
亦即,#
和题目的名字之间至多要有 1 个空格。此外,题目的名字之后也容许呈现空格,比方输出题目时,不小心引入的。因而,对于匹配文档大节题目而言,更准确一些的正则表达式是
^#[[:blank:]]+.+$
其中,[[:blank:]]
可匹配空白字符,它涵盖了空格。+
示意位于它后面的字符可能存在 1 个或更多个。*
示意位于它后面的字符可能不存在,也可能存在 1 个或更多个。.
可匹配任意一个字符。因而 [[:blank:]]+
可匹配 1 个或更多个空格,.+
可匹配 1 个或更多个字符,而 [[:blank:]]*
可匹配 0 个,1 个或更多个空格。应用这个正则表达式,便可更为稳准地匹配文档大节题目了,例如:
(setq x "# Hello world!")
(setq r "^#[[:blank:]]+.+[[:blank:]]*$")
(string-match r x)
string-match
的求值后果为 0,是正确的。当初能够思考,假使自行定义一个相似性能的文本匹配函数,其工作量,以我当初的 Elisp 编程技能以及对 NFA(不确定的有穷自动机)的理解水平,不敢预计。
正则表达式不仅仅用于匹配,也能用于文本捕捉。例如,从上述示例里的字符串 x
中捕捉文档大节题目名 Hello world!
,对应的正则表达式该当写为
(setq r "^#[[:blank:]]+\\(.+\\)[[:blank:]]*$")
亦即,在正则表达式中应用 \\(
和 \\)
将要捕捉的文本对应的正则表达式段 .+
蕴含起来。string-match
应用这个正则表达式进行文本匹配时,会将 \\(
和 \\)
蕴含的 .+
匹配到的文本段保留下来,需应用 (match-string 1)
提取。例如
(setq x "# Hello world!")
(setq r "^#[[:blank:]]+\\(.+\\)[[:blank:]]*$")
(string-match r x)
(princ\' (match-string 1 x))
上述程序输入 Hello world!
。
match-string
的第 1 个参数是正则表达式中 \\(...\\)
的序号。因为一个正则表达式里能够有多处 \\(..\\))
,因而需在 match-string
中指定要获取的文本是哪一处 \\(...\\)
捕捉的。
上面这个程序应用了两处正则表达式捕捉
(setq x "############ Hello world!")
(setq r "^\\(#+\\)[[:blank:]]+\\(.+\\)[[:blank:]]*$")
(string-match r x)
(princ\' (match-string 1 x))
(princ\' (match-string 2 x))
输入:
############
Hello world!
以上所述的仅仅是正则表达式的一些基本知识,因为以后的次要问题是如何在 Elisp 程序中应用正则表达式匹配文本。至于正则表达式自身的更多常识,能够在遇到理论问题时,长期抱抱佛脚 1。
下一章:缓冲区变换
- 见 https://www.gnu.org/software/…