all-first-registers-to-strings

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

序言

众所周知,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的定义如下:

(ql:quickload 'cl-ppcre)

(defun all-first-registers-to-strings (regex target-string)
  "返回TARGET-STRING中所有匹配正则表达式REGEX的字符串属于第一个register的内容。"
  (check-type regex string)
  (check-type target-string string)
  (let ((pos 0)
        (strs '()))
    (loop
       (multiple-value-bind (start end register-begins register-ends)
           (cl-ppcre:scan regex target-string :start pos)
         (unless start
           (return-from all-first-registers-to-strings (nreverse strs)))

         (push (subseq target-string (svref register-begins 0) (svref register-ends 0)) strs)
         (setf pos end)))))
  • 为了不停地在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的用法纷繁复杂如天上的星星,多半可以用更优雅的方法来重写一遍,这个就留给各位读者作为私下的乐趣吧。

阅读原文

你可能感兴趣的:(commonlisp,lisp,字符串,后端)