乍听之下,不无道理;仔细揣摩,胡说八道

0%

all-first-registers-to-strings

本文是《实战Common Lisp》系列的第一篇文章。本系列主要讲述在使用Common Lisp时能派上用场的小函数,希望能为Common Lisp的复兴做一些微小的贡献。

序言

众所周知,Common Lisp没有内置多少处理字符串的函数,下面的代码便能看到所有以STRING开头的函数名:

1
2
3
4
(do-external-symbols (s :cl)
(when (and (fboundp s)
(equal (search "STRING" (symbol-name s)) 0))
(print s)))

屈指可数,而且大部分是比较两个字符串的!很多其它语言中的“标配”是不存在的,比如想要将多个字符串连接起来这么简单的功能都没有!要么自己实现,要么依赖第三方库,比如cl-str提供的join函数。

标准库也没有内置正则表达式,好在有一个优秀的第三方库可以用:cl-ppcre。一种使用正则的常见需求是提取符合某种模式的内容,比如提取一篇Markdown文章中所有的图片链接。图片链接可以由下列正则括号的部分匹配:

1
"!\\[.*\\]\\((.*)\\)"

在cl-ppcre中这个括号匹配的部分叫做第一个register。我想要写一个能够提取出字符串中所有符合这段正则的第一个register,分两步走:

  1. 先实现一个all-first-registers-to-strings函数,实现通用的、提取一个字符串中所有符合某段正则的子串的第一个register的内容;
  2. 基于all-first-registers-to-strings实现一个extract-image-paths

all-first-registers-to-strings

要实现这个函数,需要借助cl-ppcre的scan函数。根据scan函数的文档,当正则匹配成功时,它的第三、第四个返回值表示正则中register的起点和终点在字符串中的偏移——它们是两个数组,起点和终点一一对应。有了起点和终点的偏移,再使用CL:SUBSEQ便能提取出register对应的子串。如果要把字符串中所有匹配register的内容都拿出来,就反复调用scan函数,直到再也没有匹配成功为止。

all-first-registers-to-strings的定义如下:

  • 为了不停地在target-string中前进,用变量pos存储scan函数的第二个返回值,并作为下一次调用scan时的start参数——显然,pos的初始值为0;
  • 为了跳出loop,使用了return-from直接从函数返回;
  • push收集最终结果,再用nreverse处理成最终的返回值——印象中《On Lisp》也说过这是一种比较常见的手法。

后记

有了all-first-registers-to-strings,就可以轻松实现extract-image-paths了:

1
2
3
(defun extract-image-paths (content)
"从博文内容CONTENT中提取出图片的绝对路径。"
(all-first-registers-to-strings "!\\[.*\\]\\((.*)\\)" content))

显然,这个all-first-registers-to-strings函数实现得很糟糕,尤其是loop的用法实在是太不Lispy了。Common Lisp的loop的用法纷繁复杂如天上的星星,多半可以用更优雅的方法来重写一遍,这个就留给各位读者作为私下的乐趣吧。

Liutos wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
你的一点心意,我的十分动力。