all-first-registers-to-strings

众所周知,

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

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

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

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

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

在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 了:

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

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

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章