跳转到内容

通用 Lisp/外部库/CL-PPCRE

来自维基教科书,开放的书籍,开放的世界

Common Lisp 可移植 Perl 兼容正则表达式库,或 CL-PPCRE,将 Perl 正则表达式的强大功能带到 Common Lisp。用作者 Edi Weitz 的话说,CL-PPCRE 具有以下特点

  • 它与 Perl 兼容。
  • 它速度很快。
  • 它可以在符合 ANSI 标准的 Common Lisp 实现之间移植。
  • 它是线程安全的。
  • 除了像 Perl 一样将正则表达式指定为字符串之外,您还可以使用 S 表达式。
  • 它附带 BSD 风格的许可证,因此您可以基本上随心所欲地使用它。

基本用法

[编辑 | 编辑源代码]

CL-PPCRE 的主要入口点是scan函数。Scan接受一个正则表达式(或 regex)和一个要匹配的字符串,并返回 regex 的匹配开始和结束索引,以及您在 regex 中定义的任何寄存器的开始和结束索引。

CL-USER> (scan "b(.)r" "foo bar baz bur")
4
7
#(5)
#(6)

第一个和第二个返回值分别是匹配子字符串的开始和结束。第三和第四个返回值标记寄存器匹配的开始和结束索引。注意,它只找到了第一个实例 bar。要找到下一个实例,您可以为关键字参数传递值:start. 不用说,您也可以通过指定 :end 关键字来限制扫描在字符串上运行的距离。

;; Match the next instance of "b.r" at or after position 5
CL-USER> (scan "b.r" "foo bar baz bur" :start 5)
12
15
#()
#()

;; This fails, because there is no match between 5 and 13 
;; (even though the regex has started at 12, it doesn't 
;; finish by 13)
CL-USER> (scan "b.r" "foo bar baz bur" :start 5 :end 13)
NIL

您可能已经注意到,在扫描字符串时跟踪起点可能有点繁琐。为此,CL-PPCRE 为常见任务提供了一些方便的函数和宏,例如

  • do-scans
  • scan-to-strings
  • do-matches
  • do-register-groups
  • all-matches
  • split
  • regex-replace
  • regex-replace-all

正则表达式作为树和闭包

[编辑 | 编辑源代码]

虽然 CL-PPCRE 将正则表达式作为字符串,但实际上它会将该字符串解析为正则表达式树。除了是 Lisp 的做法之外,这也消除了正则表达式的许多神秘之处,但是,它用冗长性来交换它。一个很好的特性是,由于您手头有正则表达式的解析树,因此可以直观地以编程方式动态构建或修改正则表达式。

;;; The unexported function cl-ppcre::parse-string parses a 
;;; regex string into a regex tree.  This allows us to see
;;; how strings translate to trees.

;; Character alternatives, Classes, wildcards
CL-USER> (cl-ppcre::parse-string "[abcdef][^abcdef]")
(:SEQUENCE (:CHAR-CLASS #\a #\b #\c #\d #\e #\f)
 (:INVERTED-CHAR-CLASS #\a #\b #\c #\d #\e #\f))
;; Note the double backslashes standard Lisp technique to 
;; get a literal backslash
CL-USER> (cl-ppcre::parse-string ".\\d\\D\\w\\W\\s\\S")
(:SEQUENCE :EVERYTHING :DIGIT-CLASS :NON-DIGIT-CLASS :WORD-CHAR-CLASS 
 :NON-WORD-CHAR-CLASS :WHITESPACE-CHAR-CLASS :NON-WHITESPACE-CHAR-CLASS)

;; Repetitions (Note that "a*?" doesn't make much sense as 
;; it will always match the empty string, but it does this
;; correctly)
CL-USER> (cl-ppcre::parse-string "Greedy:a*b+c{2,5}d{2,}Non-greedy:a*?b+?c{2,5}?d{2,}?")
(:SEQUENCE
 "Greedy:"
 (:GREEDY-REPETITION 0 NIL #\a)
 (:GREEDY-REPETITION 1 NIL #\b)
 (:GREEDY-REPETITION 2 5 #\c)
 (:GREEDY-REPETITION 2 NIL #\d)
 "Non-greedy:"
 (:NON-GREEDY-REPETITION 0 NIL #\a)
 (:NON-GREEDY-REPETITION 1 NIL #\b)
 (:NON-GREEDY-REPETITION 2 5 #\c)
 (:NON-GREEDY-REPETITION 2 NIL #\d))

;; Alternatives
CL-USER> (cl-ppcre::parse-string "a|b|c")
(:ALTERNATION #\a #\b #\c)

;; Groups, Registers, and Back References.
CL-USER> (cl-ppcre::parse-string "(a..)+(?:def)+\\1")
(:SEQUENCE
 (:GREEDY-REPETITION 1 NIL (:REGISTER (:SEQUENCE #\a :EVERYTHING :EVERYTHING)))
 (:GREEDY-REPETITION 1 NIL (:GROUP "def")) (:BACK-REFERENCE 1))
;; This matches strings like "abcdefdefabc"

在获得正则表达式的树形形式后,CL-PPCRE 使用函数create-scanner来编译该表示形式(许多 Lisp 编译为本地机器代码)。这做两件事:(1)它往往使正则表达式扫描非常快,并且(2)它往往使第一次定义正则表达式变得很昂贵(由于编译开销,尽管有一些变量可以设置为减少这种开销)。编译完成后,您可以重复使用同一个表达式来规避编译开销。CL-PPCRE 还使用适当的算法生成高效的正则表达式表示,即不是基于堆栈的系统。总的来说,这是一个非常高效的库。

;; Create a scanner closure that finds matches that resemble IPs
;; and fill registers with the numbers
CL-USER> (cl-ppcre::create-scanner "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})")
#<CLOSURE (LAMBDA (STRING CL-PPCRE::START CL-PPCRE::END)) {12E62C3D}>
NIL

;; using * as last return value
CL-USER> (cl-ppcre::scan-to-strings * "192.168.1.255")
"192.168.1.255"
#("192" "168" "1" "255")

好吧,使用 CL-PPCRE 与使用任何其他正则表达式引擎并没有太大区别。在这里,我们将看到一些快速而肮脏的使用示例。如果您在如何执行某个操作时遇到困难,请查阅正则表达式教程,perlre手册页,或 Perl 用户。您只需要记住将您的 regexs 双反斜杠(就像您始终必须执行的操作一样,将文字反斜杠插入 Lisp 字符串中)。

扫描 HTML 标签

[编辑 | 编辑源代码]
CL-USER> (defparameter *url-regex*  "((([A-Za-z]{3,9}:(?://)?)(?:[-;:&=+$,\\w]+@)?[A-Za-z0-9.-]+|(?:www\\.|[-;:&=+$,\\w]+@)[A-Za-z0-9.-]+)((?:/[+~%/.\\w-]*)?\\??(?:[-+=&;%@.\\w]*)#?(?:[.!/\\w]*))?)")

CL-USER> (cl-ppcre::parse-string *url-regex*)
(:REGISTER                                                                                                                                                                                                                                                     
 (:SEQUENCE                                                                                                                                                                                                                                                    
  (:REGISTER                                                                                                                                                                                                                                                   
   (:ALTERNATION                                                                                                                                                                                                                                               
    (:SEQUENCE (:REGISTER (:SEQUENCE (:GREEDY-REPETITION 3 9 (:CHAR-CLASS (:RANGE #\A #\Z) (:RANGE #\a #\z))) #\: (:GREEDY-REPETITION 0 1 (:GROUP "//"))))                                                                                                     
     (:GREEDY-REPETITION 0 1 (:GROUP (:SEQUENCE (:GREEDY-REPETITION 1 NIL (:CHAR-CLASS #\- #\; #\: #\& #\= #\+ #\$ #\, :WORD-CHAR-CLASS)) #\@)))                                                                                                               
     (:GREEDY-REPETITION 1 NIL (:CHAR-CLASS (:RANGE #\A #\Z) (:RANGE #\a #\z) (:RANGE #\0 #\9) #\. #\-)))                                                                                                                                                      
    (:SEQUENCE (:GROUP (:ALTERNATION "www." (:SEQUENCE (:GREEDY-REPETITION 1 NIL (:CHAR-CLASS #\- #\; #\: #\& #\= #\+ #\$ #\, :WORD-CHAR-CLASS)) #\@)))                                                                                                        
     (:GREEDY-REPETITION 1 NIL (:CHAR-CLASS (:RANGE #\A #\Z) (:RANGE #\a #\z) (:RANGE #\0 #\9) #\. #\-)))))                                                                                                                                                    
  (:GREEDY-REPETITION 0 1                                                                                                                                                                                                                                      
   (:REGISTER                                                                                                                                                                                                                                                  
    (:SEQUENCE (:GREEDY-REPETITION 0 1 (:GROUP (:SEQUENCE #\/ (:GREEDY-REPETITION 0 NIL (:CHAR-CLASS #\+ #\~ #\% #\/ #\. :WORD-CHAR-CLASS #\-)))))                                                                                                             
     (:GREEDY-REPETITION 0 1 #\?) (:GROUP (:GREEDY-REPETITION 0 NIL (:CHAR-CLASS #\- #\+ #\= #\& #\; #\% #\@ #\. :WORD-CHAR-CLASS)))                                                                                                                           
     (:GREEDY-REPETITION 0 1 #\#) (:GROUP (:GREEDY-REPETITION 0 NIL (:CHAR-CLASS #\. #\! #\/ :WORD-CHAR-CLASS))))))))                                                                                                                                          
                                                                                                      
CL-USER> (cl-ppcre::scan-to-strings *url-regex* "yo mailto:[email protected] asd http://foo.com")
"mailto:[email protected]"                                                                                                                         
#("mailto:[email protected]" "mailto:[email protected]" "mailto:" "")

/etc/passwd

中查找用户

[编辑 | 编辑源代码]

进一步阅读

[编辑 | 编辑源代码]

http://www.weitz.de/cl-ppcre/ Edi Weitz 的 CL-PPCRE 页面
华夏公益教科书