通用 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 字串中)。
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:will@foo.com asd http://foo.com")
"mailto:will@foo.com"
#("mailto:will@foo.com" "mailto:will@foo.com" "mailto:" "")