此 EEP 建議如何將外部正規表示式函式庫整合到 Erlang 虛擬機器中。
正規表示式被廣泛使用。無論如何使用語言的其他功能來匹配或選取字串的部分,許多程式設計師偏好正規表示式的語法,並期望現代語言能提供正規表示式。
Perl 程式語言已將正規表示式直接整合到語法中,Perl 程式設計師通常非常擅長編寫複雜的正規表示式,來剖析例如文字檔、HTTP 請求或簡單的使用者輸入。Perl 對常見正規表示式的擴充是廣為人知的,許多現代程式語言都支援類似的功能。
Erlang 目前有一個極簡的正規表示式模組 (STDLIB 中的 regexp 模組),它缺少其他實作中常見的功能。與其他語言中使用的原生 C 函式庫相比,目前的函式庫也慢得令人痛苦。
Erlang 需要以一種不會破壞虛擬機器特性的方式,與現代正規表示式函式庫介接。
已經嘗試純粹使用 Erlang 撰寫更有效率的正規表示式函式庫,但到目前為止,尚未提出真正有效率的實作,而且創建一個這樣的實作所需的工作量被認為是巨大的。
另一方面,已經提出幾項或多或少成功地將外部正規表示式函式庫整合到虛擬機器的嘗試。然而,它們都沒有解決長時間運行的正規表示式使排程器停滯的問題。
Erlang VM 中的內建函數需要在執行一定次數的迭代後停止執行,以避免排程器停滯,從而使系統中的其他進程處於飢餓狀態。當 Erlang 進程再次被排程時,內建函數會重新啟動,並提供某種方式來儲存其目前狀態,以便函數的執行可以從上次停止的地方繼續。
正規表示式匹配的執行在許多方面與虛擬機器執行普通 beam 程式碼類似,但可用的函式庫(原因很明顯)並沒有準備好暫時放棄執行,以允許其他進程執行。對大量主題資料執行複雜的正規表示式可能需要數秒甚至數分鐘才能執行。在真正的並行系統中,使 VM 中的一個排程器停滯這麼長的時間是不可行的。由於建議的外部函式庫介面從未解決過這個問題,因此沒有任何一個被接受和/或整合到主要的 Erlang 發行版中。
堆疊使用是另一個很少被解決的問題。Erlang 虛擬機器可能會執行大量的排程器執行緒,尤其是在具有大量核心的處理器上。多執行緒應用程式需要小心堆疊的使用,因此最好避免遞迴 C 常式。Erlang 虛擬機器避免在 C 程式碼中使用遞迴,因此連結的函式庫也應該這樣做。當涉及即時作業系統時,避免在 C 程式碼中使用遞迴的需求更加明顯。用於 Erlang 正規表示式的函式庫不能在 C 堆疊上遞迴,至少不能以在編譯時無法確定堆疊使用量的方式遞迴。
當應該排程另一個 Erlang 進程時,中斷正規表示式(或其他長時間操作)的執行問題有兩種明顯的解決方案
計算正規表示式匹配中的迭代次數,在一定次數的迭代後(或一定時間後)儲存狀態,並在超過執行時間時將控制權返回給排程器。
讓作業系統透過在單獨的核心執行緒中執行正規表示式匹配來處理這個問題。
在虛擬機器的檔案驅動程式中,使用第二種方法,引入了非同步執行緒池的概念。然而,檔案 I/O 的情況很特殊,因為 I/O 操作本身通常比使用非同步執行緒時涉及的執行緒間通訊和任務切換的運行時間消耗更多時間。此外,I/O 根本沒有其他解決方案,因此作業系統執行緒是這種情況下唯一可用的解決方案。
如果正規表示式要在單獨的執行緒中執行,即使是非常小且簡單的表達式也必須承受作業系統層級任務切換和通訊的額外負擔。
虛擬機器中的其他長時間操作使用自願中斷和重新排程的第一種方法。在涉及外部函式庫的情況下,例如 IP 通訊,模擬器透過提供 I/O 多工處理(select/poll)的介面,提供被動等待事件的方式。這是避免在大多數驅動程式中阻塞排程器的方法。只有在根本沒有其他選項時才使用非同步執行緒,例如在檔案 I/O 中(無法使用 I/O 多工處理)。
在驅動程式或 BIF 中介接外部函式庫時使用第一種解決方案,需要找到一種可以中斷和重新啟動執行的函式庫,或者修改現有函式庫以支援此功能。
即使修改函式庫會使函式庫的升級和修補變得更加困難,但好處是顯著的。例如,在執行正規表示式時,將會使用實際執行 beam 程式碼的同一個執行緒,因此設定時間和整體開銷保持在最低限度。當然,regexp 本身的執行時間會稍微長一點,因為實作需要追蹤執行的迭代次數,並且需要準備好儲存目前狀態以供稍後執行喚醒。但是,當涉及較小的正規表示式(或者說是涉及少量迴圈的表達式)時,預計較小的設定時間將佔主導地位。還必須牢記,此解決方案對作業系統排程器施加的負載要小得多,這對於大型和/或嵌入式系統來說是一件好事。
對於沒有核心執行緒的作業系統,第一種解決方案是唯一可接受的解決方案。對於純使用者空間程式碼執行,單獨的執行緒對 Erlang 系統的即時特性弊大於利。
在理想情況下,整合到虛擬機器中的函式庫應滿足以下期望
目前沒有可用的正規表示式函式庫能提供完美的匹配。目前最佳的選擇是 PCRE 函式庫,它具有編譯時選項,可選擇不使用 C 堆疊、與 Perl(和 Python)相容的正規表示式,並且以結構良好的方式編寫,使其適合整合、移植和實作 Erlang 案例中需要的擴充功能。
其他替代方案包括 rxlib(不再維護)、Tcl/Tk 正規表示式實作、GNU regex、Jakarta 和 Onigurama 等。其中 Tcl/Tk 實作看起來最有希望,特別是因為在許多情況下它比其他實作快得多。然而,演算法和程式碼相當難以理解,而且正規表示式的風味並不是最廣泛的。
在仔細研究了這些替代方案後,我得出結論,PCRE 是最佳選擇,原因如下
雖然關於程式碼可讀性的主觀推理可能看起來有些不合時宜,但 PCRE 程式碼庫使函式庫的更新更容易整合,因為只需要對函式庫進行相對較少且易於理解的修改,即可使其適合虛擬機器。能夠維護函式庫非常重要,並且能夠理解程式碼至關重要。
然而,該函式庫最吸引人的功能是對與 Perl 相容的正規表示式的廣泛支援。PCRE 無疑是現今功能最強大的函式庫之一,習慣使用 Perl 正規表示式的 Erlang 程式設計師將會感到賓至如歸。
在 Perl 中,正規表示式已整合到語言本身中。當然,這也可以在 Erlang 中完成。然而,Erlang 已經具有匹配結構化資料和二進位資料的語法。為使用正規表示式進行字串匹配引入新的原語似乎不合時宜。Erlang 也不是一種設計用於以 Perl 的方式處理文字資料的語言,而是一種可以處理複雜結構化資料的語言。然而,位元語法有一天可能會受益於正規表示式擴充功能,但這超出了此 EEP 的範圍。
透過內建函數與函式庫介接的正規表示式模組是 Erlang 中常用的方法,這也是此 EEP 建議的方法。由於模組名稱 regexp 已被使用,因此使用模組名稱的縮寫 “re” 似乎是一個不錯的選擇。
作為基礎實作,我建議使用一個包含兩個基本功能的模組:一個用於將正規表達式預編譯成「位元組碼」,以便執行正規表達式匹配;另一個用於實際執行正規表達式匹配。執行匹配的函數應該接受已編譯的正規表達式或正規表達式的原始碼作為輸入(連同主體字串和執行選項)。
圍繞這兩個建議的函數,可以在 Erlang 中實作功能,以模擬現有的正規表達式函式庫或實作新的功能。
目前的 regexp 模組除了匹配之外,還可以根據正規表達式分割字串(功能類似於 Perl 內建的 split 函數),並根據正規表達式匹配來替換子字串(例如 Perl 或 awk 中的 s/)。
函數的名稱應該盡可能選擇避免與目前的 regexp 函式庫函數混淆,因此我建議將正規表達式編譯、執行和替換的名稱分別定為 "compile"、"run" 和 "replace"。由於沒有出現 "split" 名稱的合適同義詞,因此新模組中保留該名稱。
以下是建議的手冊頁面的一部分
iodata() = iolist() | binary()
iolist() = [char() | binary() | iolist()]
% a binary is allowed as the tail of the list
mp() = Opaque datatype containing a compiled regular expression.
型態
Regexp = iodata()
與 compile(Regexp,[]) 相同
型態
Regexp = iodata()
Options = [ Option ]
Option = anchored | caseless | dollar_endonly | dotall | extended |
firstline | multiline | no_auto_capture | dupnames |
ungreedy | {newline, NLSpec}
NLSpec = cr | crlf | lf | anycrlf
MP = mp()
ErrSpec = {ErrString, Position}
ErrString = string()
Position = int()
此函數將以下描述語法的正規表達式編譯為內部格式,以便稍後用作 run/2,3 函數的參數。
如果相同的表達式要在程式生命週期中多次用於匹配多個主體,則在匹配之前編譯正規表達式會很有用。編譯一次並執行多次比每次想要匹配時都編譯效率高得多。
選項具有以下含義
anchored
強制模式「錨定」,也就是說,它被限制為僅在被搜尋的字串(「主體字串」)中的第一個匹配點進行匹配。也可以透過模式本身的適當結構來實現此效果。
caseless
模式中的字母匹配大寫字母和小寫字母。它等效於 Perl 的 /i
選項,並且可以使用 (?i)
選項設定在模式中進行更改。大寫字母和小寫字母的定義與 ISO-8859-1 字元集中的定義相同。
dollar_endonly
模式中的美元符號元字元僅在主體字串的末尾匹配。如果沒有此選項,美元符號也會在字串末尾的新行之前立即匹配(但不會在任何其他新行之前匹配)。如果給定 multiline,則 dollar_endonly 選項將被忽略。Perl 中沒有等效的選項,也沒有在模式中設定它的方法。
dotall
模式中的點元字元匹配所有字元,包括指示新行的字元。如果沒有此選項,當目前位置在新行時,點不會匹配。此選項等效於 Perl 的 /s
選項,並且可以使用 (?s)
選項設定在模式中進行更改。負向類別(例如 [^a]
)始終匹配新行字元,與此選項的設定無關。
extended
模式中的空白資料字元將被忽略,除非它們被逸出或在字元類別內。空白不包括 VT 字元 (ASCII 11)。此外,字元類別外未逸出的 #
和下一個新行(包括)之間的字元也會被忽略。這等效於 Perl 的 /x
選項,並且可以使用 (?x)
選項設定在模式中進行更改。此選項可以讓您在複雜模式中包含註解。但是,請注意,這僅適用於資料字元。空白字元永遠不會出現在模式中的特殊字元序列中,例如在引入條件子模式的序列 (?(
中。
firstline
非錨定的模式必須在主體字串中的第一個新行之前或在其處進行匹配,儘管匹配的文字可能會繼續跨越新行。
multiline
預設情況下,PCRE 將主體字串視為由單行字元組成(即使它實際上包含新行)。「行首」元字元 (^
) 僅在字串的開頭匹配,而「行尾」元字元 ($
) 僅在字串的末尾或在終止新行之前匹配(除非給定 dollar_endonly)。這與 Perl 相同。
當給定 multiline 時,「行首」和「行尾」結構分別在主體字串中的內部新行之後或之前以及在最開始和最末尾匹配。這等效於 Perl 的 /m
選項,並且可以使用 (?m)
選項設定在模式中進行更改。如果主體字串中沒有新行,或者模式中沒有出現 ^
或 $
,則設定 multiline 無效。
no_auto_capture
停用模式中編號的捕獲括號的使用。任何不跟隨 ?
的左括號的行為都好像它跟隨 ?:
,但命名括號仍可用於捕獲(並且它們以通常的方式取得數字)。Perl 中沒有與此選項等效的選項。
dupnames
用於識別捕獲子模式的名稱不必是唯一的。當已知只有一個命名的子模式實例可以被匹配時,這對於某些類型的模式很有幫助。以下是命名子模式的更多詳細資訊。
ungreedy
此選項會反轉量詞的「貪婪」行為,因此它們預設情況下不是貪婪的,但如果後面跟隨 ?
,則會變得貪婪。它與 Perl 不相容。也可以透過模式中的 (?U)
選項設定來設定。
{newline, NLSpec}
覆寫主體字串中新行的預設定義,在 Erlang 中為 LF (ASCII 10)。
cr
lf
crlf
anycrlf
型態
Subject = iodata()
RE = mp() | iodata()
Captured = [ CaptureData ]
CaptureData = {int(),int()} | string() | binary()
ErrSpec = {ErrString, Position}
ErrString = string()
Position = int()
與 run(Subject,RE,[]) 相同。
型態
Subject = iodata()
RE = mp() | iodata()
Options = [ Option ]
Option = anchored | global | notbol | noteol | notempty | {offset, int()} |
{newline, NLSpec} | {capture, ValueSpec} |
{capture, ValueSpec, Type} | CompileOpt
Type = index | list | binary
ValueSpec = all | all_but_first | first | ValueList
ValueList = [ ValueID ]
ValueID = int() | string() | atom()
CompileOpt = see compile/2 above
NLSpec = cr | crlf | lf | anycrlf
Captured = [ CaptureData ] | [ [ CaptureData ] ... ]
CaptureData = {int(),int()} | string() | binary()
ErrSpec = {ErrString, Position}
ErrString = string()
Position = int()
執行正規表達式匹配,傳回 match
/ {match, Captured}
或 nomatch
。正規表達式可以作為 iodata() 給定,在這種情況下,它會自動編譯(如 re:compile/2)並執行,或者作為預先編譯的 mp() 給定,在這種情況下,它會直接針對主體執行。
當涉及編譯時,該函數可能會傳回編譯錯誤,就像單獨編譯時一樣 ({error, {string(),int()}}
);當僅匹配時,不會傳回任何錯誤。
如果正規表達式先前已編譯,則選項清單只能包含選項 anchored
、global
、notbol
、noteol
、notempty
、{offset, int()}
、{newline, NLSpec}
和 {capture, ValueSpec}
/ {capture, ValueSpec, Type}
。否則,re:compile/2
函數的所有有效選項也都允許使用。允許同時用於編譯和執行匹配的選項,即 anchored
和 {newline, NLSpec}
,如果與非預先編譯的正規表達式一起出現,將會影響編譯和執行。
{capture, ValueSpec}
/ {capture, ValueSpec, Type}
定義在成功匹配時從函數傳回的內容。捕獲元組可以同時包含一個值規範,指示要傳回哪些捕獲的子字串,以及一個類型規範,指示如何傳回捕獲的子字串(作為索引元組、清單或二進位)。捕獲選項使函數非常靈活且功能強大。以下詳細說明不同的選項
如果捕獲選項描述為完全不進行子字串捕獲 ({capture, none}
),則該函數將在成功匹配時傳回單個原子 match,否則將傳回元組 {match, ValueList}
。可以透過指定 none 或空清單作為 ValueSpec 來停用捕獲。
以下是與執行相關的所有選項的說明
anchored
限制 re:run/3
僅在第一個匹配位置進行匹配。如果模式是用 anchored 編譯的,或因其內容而變為錨定的,則無法在匹配時使其非錨定,因此沒有 unanchored 選項。
global
實作全域(重複)搜尋,如 i.e. Perl 中的 /g
旗標。找到的每個匹配項都會以單獨的 list() 傳回,其中包含特定的匹配項以及任何匹配的子表達式(或由捕獲選項指定)。因此,當給定此選項時,傳回值的 Captured 部分將是 list() 的 list()。
當正規表達式匹配一個空字串時,其行為可能看起來不直觀,因此需要澄清其行為。使用 global 選項時,re:run/3
處理空匹配的方式與 Perl 相同,這表示在任何給出空字串(長度為 0)的點進行匹配時,也會使用選項 [anchored, notempty]
重試。如果該搜尋給出長度 > 0 的結果,則會包含該結果。範例
re:run("cat","(|at)",[global]).
匹配將按以下方式執行
在偏移量 0
正規表達式 (|at)
將首先在字串 cat 的初始位置匹配,給出結果集 [{0,0},{0,0}]
(第二個 {0,0}
是由於括號標記的子表達式所致)。由於匹配的長度為 0,我們尚不前進到下一個位置。
在偏移量 0,使用 [anchored, notempty]
搜尋在相同位置使用選項 [anchored, notempty] 重新嘗試,但沒有產生任何更有趣的較長結果,因此搜尋位置現在前進到下一個字元 (a
)。
在偏移量 1
現在的搜尋結果為 [{1,0}, {1,0}]
,表示此搜尋也將使用額外選項重複進行。
在偏移量 1,使用 [anchored, notempty]
現在找到 ab 的替代方案,結果將是 [{1,2}, {1,2}]
。結果會加入到結果列表中,且搜尋字串中的位置會前進兩步。
在偏移量 3
搜尋現在再次比對到空字串,產生 [{3,0}, {3,0}]
。
在偏移量 1,使用 `[anchored, notempty]`
這將不會產生長度 > 0 的結果,且我們已在最後一個位置,因此全域搜尋完成。
呼叫的結果為
{match,[[{0,0},{0,0}],[{1,0},{1,0}],[{1,2},{1,2}],[{3,0},{3,0}]]}
notempty
如果提供此選項,則空字串不被視為有效的比對。如果模式中有替代方案,則會嘗試這些方案。如果所有替代方案都比對到空字串,則整個比對會失敗。例如,如果模式
a?b?
應用於非以 “a” 或 “b” 開頭的字串,它會比對到主體的開頭處的空字串。如果給定 notempty,則此比對無效,因此 re:run/3
會在字串中進一步搜尋 “a” 或 “b” 的出現位置。
Perl 沒有與 notempty 直接對應的功能,但它確實針對其 split()
函數以及使用 /g
修飾符時的空字串模式比對做了特殊處理。在比對到空字串後,可以透過先在相同偏移量使用 notempty 和 anchored 再次嘗試比對,然後如果失敗,則透過前進起始偏移量(見下文)並再次嘗試一般比對來模擬 Perl 的行為。
notbol
此選項指定主體字串的第一個字元不是行的開頭,因此脫字符號不應在其之前比對。在沒有 multiline(在編譯時)的情況下設定此選項,會導致脫字符號永遠無法比對。此選項僅影響脫字符號的行為。它不會影響 \A
。
noteol
此選項指定主體字串的結尾不是行的結尾,因此美元符號不應比對到它,也不應比對到(多行模式除外)緊接在其前的換行符號。在沒有 multiline(在編譯時)的情況下設定此選項,會導致美元符號永遠無法比對。此選項僅影響美元符號的行為。它不會影響 \Z
或 \z
。
{offset , int()}
從主體字串中給定的偏移量(位置)開始比對。偏移量從零開始,因此預設值為 {offset,0}
(整個主體字串)。
{newline, NLSpec}
覆寫主體字串中新行的預設定義,在 Erlang 中為 LF (ASCII 10)。
cr
lf
crlf
anycrlf
{capture, ValueSpec}
/ {capture, ValueSpec, Type}
指定要傳回哪些捕獲的子字串以及以何種格式傳回。預設情況下,re:run/3
會捕獲子字串的所有比對部分以及所有捕獲子模式(會自動捕獲模式的所有部分)。預設傳回類型是字串捕獲部分的(從零開始)索引,以 {Offset,Length}
配對(捕獲的索引類型)給出。
作為預設行為的範例,以下呼叫
re:run("ABCabcdABC","abcd",[]).
會傳回,作為第一個也是唯一捕獲的字串,主體的比對部分(中間的 “abcd”)作為索引配對 {3,4}
,其中字元位置從零開始,就像偏移量一樣。上述呼叫的傳回值將會是
{match,[{3,4}]}
另一個(相當常見的)案例是正規表示式比對到整個主體,例如
re:run("ABCabcdABC",".*abcd.*",[]).
傳回值會對應地指出整個字串,從索引 0 開始,長度為 10 個字元
{match,[{0,10}]}
如果正規表示式包含捕獲子模式,如下例所示
re:run("ABCabcdABC",".*(abcd).*",[]).
會捕獲所有比對的主體,以及捕獲的子字串
{match,[{0,10},{3,4}]}
完整的比對模式始終會給出列表中的第一個傳回值,其餘子模式會按照它們在正規表示式中出現的順序加入。
捕獲元組的建構方式如下
ValueSpec
指定要傳回哪些捕獲的(子)模式。ValueSpec
可以是一個原子,描述一組預定義的傳回值,也可以是一個列表,其中包含要傳回的特定子模式的索引或名稱。
預定義的子模式集為
all
所有捕獲的子模式,包括完整的比對字串。這是預設值。
first
僅第一個捕獲的子模式,它始終是主體的完整比對部分。所有明確捕獲的子模式都會被捨棄。
all_but_first
除了第一個比對子模式之外的所有模式,即所有明確捕獲的子模式,但不包括主體字串的完整比對部分。如果正規表示式整體上比對到主體的很大一部分,但您感興趣的部分在明確捕獲的子模式中,這會很有用。如果傳回類型是列表或二進位,則不傳回您不感興趣的子模式是優化的一個好方法。
none
根本不傳回比對的子模式,成功比對時,會產生單一原子比對作為函數的傳回值,而不是 {match, list()} 傳回。指定空列表會產生相同的行為。
值列表是要傳回的子模式的索引列表,其中索引 0 代表模式的所有部分,1 代表正規表示式中的第一個明確捕獲子模式,依此類推。當在正規表示式中使用具名捕獲子模式(見下文)時,可以使用 atom()
或 string()
來指定要傳回的子模式。這值得舉一個範例,考慮以下正規表示式
".*(abcd).*"
針對字串 "ABCabcdABC"
進行比對,僅捕獲 "abcd"
部分(第一個明確子模式)
re:run("ABCabcdABC",".*(abcd).*",[{capture,[1]}]).
呼叫將產生以下結果
{match,[{3,4}]}
因為第一個明確捕獲的子模式是 "(abcd)"
,比對到主體中的 "abcd"
,在(從零開始的)位置 3,長度為 4。
現在考慮相同的正規表示式,但子模式明確命名為 'FOO'
".*(?<FOO>abcd).*"
使用此表示式,我們仍然可以使用以下呼叫來給出子模式的索引
re:run("ABCabcdABC",".*(?<FOO>abcd).*",[{capture,[1]}]).
產生與之前相同的結果。但由於子模式已命名,我們也可以在值列表中給出其名稱
re:run("ABCabcdABC",".*(?<FOO>abcd).*",[{capture,['FOO']}]).
這會產生與先前範例相同的結果,即
{match,[{3,4}]}
值列表可能會指定正規表示式中不存在的索引或名稱,在這種情況下,傳回值會因類型而異。如果類型為 index
,則會為在正規表示式中沒有對應子模式的值傳回元組 {-1,0}
,但對於其他類型(二進位和列表),值分別為空的二進位或列表。
類型
可選地指定如何傳回捕獲的子字串。如果省略,則使用預設值 index。Type 可以是以下其中之一
index
將捕獲的子字串作為主體字串的位元組索引對和主體中比對字串的長度傳回(如同在比對之前使用 iolist_to_binary 平鋪主體字串一樣)。這是預設值。
list
將比對的子字串作為字元列表(Erlang string()
)傳回。
binary
將比對的子字串作為二進位傳回。
一般來說,在比對中沒有被賦值的子模式會傳回為元組 {-1,0}
,當類型為 index
時。對於其他傳回類型,未賦值的子模式會分別傳回為空的二進位或列表。考慮以下正規表示式
".*((?<FOO>abdd)|a(..d)).*"
有三個明確捕獲的子模式,其中左括號位置決定結果中的順序,因此 "((?<FOO>abdd)|a(..d))"
是子模式索引 1,"(?<FOO>abdd)"
是子模式索引 2,"(..d)"
是子模式索引 3。當針對以下字串進行比對時
"ABCabcdABC"
索引 2 的子模式不會比對,因為字串中不存在 "abdd"
,但完整模式會比對(由於替代方案 "a(..d)"
)。因此,索引 2 的子模式未被賦值,預設傳回值將為
{match,[{0,10},{3,4},{-1,0},{4,3}]}
將捕獲 Type 設定為二進位將產生以下結果
{match,[<<"ABCabcdABC">>,<<"abcd">>,<<>>,<<"bcd">>]}
其中空二進位 (<<>>
) 代表未賦值的子模式。在二進位的情況下,會遺失一些關於比對的資訊,<<>>
可能只是一個捕獲的空字串。
如果需要在空比對和不存在的子模式之間進行區分,請使用類型索引,並在 Erlang 程式碼中轉換為最終類型。
當給定選項 global 時,捕獲規格會單獨影響每次比對,因此
re:run("cacb","c(a|b)",[global,{capture,[1],list}]).
會產生結果
{match,[["a"],["b"]]}
僅影響編譯步驟的選項在 re:compile/2
函數中描述。
型態
Subject = iodata()
RE = mp() | iodata()
Replacement = iodata()
ErrSpec = {ErrString, Position}
ErrString = string()
Position = int()
與 replace(Subject, RE, Replacement,[]) 相同。
型態
Subject = iodata()
RE = mp() | iodata()
Replacement = iodata()
Options = [ Option ]
Option = anchored | global | notbol | noteol | notempty |
{offset, int()} | {newline, NLSpec} |
{return, ReturnType} | CompileOpt
ReturnType = iodata | list | binary
CompileOpt = see compile/2 above
NLSpec = cr | crlf | lf | anycrlf
ErrSpec = {ErrString, Position}
ErrString = string()
Position = int()
將 Subject 字串的比對部分替換為 Replacement 的內容。
選項的提供方式與 re:run/3 函數相同,但 re:run/3 的 capture
選項不允許。而是存在 {return, ReturnType}
。預設傳回類型為 iodata
,其建構方式可最大限度地減少複製。iodata 結果可以直接在許多 I/O 操作中使用。如果需要扁平的 list(),請指定 {return, list}
,如果首選二進位,請指定 {return, binary}
。
替換字串可以包含特殊字元 &
,它會在結果中插入整個比對的表示式,以及特殊序列 \N
(其中 N 是大於 0 的整數),導致子表示式數字 N 會插入到結果中。如果正規表示式沒有產生具有該數字的子表示式,則不會插入任何內容。
若要在結果中插入 &
或 \
,請在其前面加上 \
。請注意,Erlang 已在文字字串中賦予 \
特殊含義,因此單個 \
必須寫成 "\\"
,因此雙 \
必須寫成 "\\\\"
。範例
re:replace("abcd","c","[&]",[{return,list}]).
會產生
"ab[c]d"
而
re:replace("abcd","c","[\\&]",[{return,list}]).
會產生
"ab[&]d"
{error, ErrSpec}
傳回值只能由編譯產生,即當給定未預編譯的格式錯誤的 RE 時。
型態
Subject = iodata()
RE = mp() | iodata()
SplitList = [ iodata() ]
ErrSpec = {ErrString, Position}
ErrString = string()
Position = int()
與 split(Subject, RE, [])
相同。
型態
Subject = iodata()
RE = mp() | iodata()
Options = [ Option ]
Option = anchored | global | notbol | noteol | notempty |
{offset, int()} | {newline, NLSpec} | {return, ReturnType} |
{parts, NumParts} | group | CompileOpt
NumParts = int() | infinity
ReturnType = iodata | list | binary
CompileOpt = see compile/2 above
NLSpec = cr | crlf | lf | anycrlf
SplitList = [ RetData ] | [ GroupedRetData ]
GroupedRetData = [ RetData ]
RetData = iodata() | binary() | list()
ErrSpec = {ErrString, Position}
ErrString = string()
Position = int()
此函數會根據提供的正規表示式尋找符記,將輸入分割成數個部分。
分割基本上是透過執行全域正規表示式比對,並在每次比對成功時分割初始字串來完成。字串中比對成功的部分會從輸出中移除。
結果會以「字串」列表的形式提供,這是返回選項中指定的偏好資料類型(預設為 iodata
)。
如果在正規表示式中提供了子表示式,比對成功的子表示式也會返回到結果列表中。範例如下:
re:split("Erlang","[ln]",[{return,list}]).
會產生以下的結果:
["Er","a","g"]
而
re:split("Erlang","([ln])",[{return,list}]).
會產生:
["Er","l","a","n","g"]
與子表示式(以正規表示式中的括號標記)比對的文字會插入到結果列表中找到的位置。實際上,這表示將整個正規表示式設為單一子表示式(如上述範例)的分割結果串連在一起,將始終產生原始字串。
由於範例中最後一部分("g"
)沒有比對的子表示式,因此之後沒有插入任何內容。為了使字串群組和與子表示式比對的部分更明顯,可以使用 group 選項,將主體字串的部分與分割字串時與子表示式比對的部分分組在一起:
re:split("Erlang","([ln])",[{return,list},group]).
會產生
[["Er","l"],["a","n"],["g"]]
這裡的正規表示式首先比對到 "l"
,導致 "Er"
成為結果中的第一部分。當正規表示式比對成功時,(唯一)子表示式會繫結到 "l"
,這就是為什麼 "l"
會與 "Er"
一起插入到群組中。下一個比對是 "n"
,使 "a"
成為下一個返回的部分。由於子表示式在此情況下繫結到子字串 "n"
,因此 "n"
會插入到此群組中。最後一個群組包含字串的其餘部分,因為未找到更多比對。
預設情況下,所有空字串都會從結果列表的末尾移除,其語意是我們盡可能將字串分割成許多部分,直到到達字串的末尾。實際上,這表示所有空字串都會從結果列表中移除(如果給定了 group 選項,則移除所有空群組)。parts
選項可用於變更此行為。讓我們看一個範例:
re:split("Erlang","[lg]",[{return,list}]).
結果將會是:
["Er","an"]
因為結尾處的 "g"
比對有效地使比對到達字串的末尾。但是,如果我們說我們需要更多部分:
re:split("Erlang","[lg]",[{return,list},{parts,3}]).
我們也會取得最後一部分,即使最後一次比對(比對 "g"
)之後只有一個空字串:
["Er","an",[]]
使用此輸入資料無法產生超過三個部分,因此:
re:split("Erlang","[lg]",[{return,list},{parts,4}]).
會產生相同的結果。若要指定要返回盡可能多的結果,包括末尾的任何空結果,您可以指定無限大作為要返回的部分數。指定 0 作為部分數會產生預設行為,即返回所有部分,但末尾的空部分除外。
如果擷取了子表示式,如果未指定 {parts, N}
,則末尾的空子表示式比對也會從結果中移除。如果您熟悉 Perl,預設行為與 Perl 預設行為完全對應,其中 N
為正整數的 {parts, N}
與 Perl 行為完全對應,且第三個參數為正數,而 {parts, infinity} 行為對應於當 Perl 例程的第三個參數為負整數時的情況。
先前未針對 re:run/3
函數描述的選項摘要:
{return, ReturnType}
指定原始字串的部分在結果列表中呈現的方式。可能的類型如下:
iodata
binary
list
group
將字串的一部分與字串中與正規表示式子表示式比對的部分分組在一起。
在這種情況下,函數的返回值將是 list()
的 list()
。每個子列表都以從主體字串中選出的字串開始,後跟與正規表示式中每個子表示式比對的部分,順序與正規表示式中出現的順序相同。
{parts, N}
指定主體字串要分割成的部分數。
部分數應為 0,以表示預設行為「盡可能多,跳過末尾的空部分」;正整數表示部分數的特定上限;而無限大則表示可能的最大部分數,無論末尾的部分是否為空字串。
如手冊摘錄所示,我建議允許將正規表示式和主體字串都以 iodata()
的形式提供,這表示二進位資料、列表或二進位資料與深層列表的混合。當不涉及 Unicode 時,這基本上表示將資料提供給 re 模組時隱含的 iolist_to_binary()
。
以下擴充尚未在原型中實作,但應包含在最終版本中:
Unicode 支援。Unicode 字串應如 EEP 10 中建議的方式表示,這表示二進位資料中的 UTF-8、整數形式的 Unicode 字元列表或它們的混合。如果正規表示式是針對 Unicode 編譯的,或者在編譯和一次執行時提供了 unicode
選項,則預期資料將採用支援的 Unicode 格式之一,否則將擲回 badarg
例外狀況。
比對述詞,以便輕鬆在邏輯 Erlang 表達式中使用正規表示式。
其中,Unicode 支援是最重要的,也是無法僅在 Erlang 程式碼中有效實作的。
使用 PCRE 程式庫的原型實作以及 R12B-4 發行版本中的參考手冊頁面。此實作尚未完全支援 Unicode,因為撰寫時不接受 EEP 10。原型實作也缺少「split」函數,該函數是在 R12B-4 發行版本之後實作的。
在效能方面,使用此原型的相當簡單的正規表示式比對速度比目前的 regexp 模組快 75 倍。當不需要外部排程時,為了允許中斷正規表示式執行而進行的簿記會消耗 1% 到 2% 的效能。在最壞的情況下,與未修改的程式庫相比,可能會注意到 5% 的效能損失,但隨後會涉及實際的重新啟動,因此這些數字並非完全可比。
預期編譯 PCRE 以使用 C 堆疊進行遞迴呼叫並避免重新啟動,將會在執行速度方面產生最佳結果。但是,在沒有重新啟動的情況下,基準測試與完全可中斷版本之間的差異僅在 1% 到 3% 的範圍內,而在實際發生重新啟動時,差異仍不超過 6%。
結論是,與理論最大值相比,為了將 PCRE 程式庫整合到 Erlang 模擬器中,而無需使用非同步執行緒,所施加的額外成本在最壞的情況下不超過 6%。
本文档已置于公共领域。