最長 token 匹配
注意,下面進入糟糕區域,如果看不懂請查看英文原文!
S05-metasyntax/longest-alternative.t lines 53–460
因為 "longest-token matching" 是一個很長的短語, 我們會經常將這個概念叫做 LTM
. 這個基本的概念就是人們在頭腦中傾向于怎么去解析文本, 所以計算機應該像人一樣嘗試做同樣的事情. 而使用 LTM
解析文本就是關于計算機怎樣決定匹配一組備選分支中的哪一個備選分支的.
在 Perl 6 中, |
代表使用聲明性的 longest-token 語義的邏輯備選分支.(你現在能使用 ||
來標示舊的暫存的備選分支. 就是, |
和 ||
現在在正則語法內的運作方式和在正則語法外的運作方式很像, 在正則語法外部, |
和 ||
代表 junctional 和 短路的 OR
. 這也包括事實上 |
的優先級比 ||
的優先級高.)
在過去, Perl 中正則表達式是通過一個能回溯的 NFA 算法來處理的. 這很強大, 但是很多解析器通過并行地處理 rules , 而不是一個接著一個地處理, 工作起來更高效, 至少達到某種程度. 如果你看一下像 yacc grammar 這樣的東西, 你會發現很多 pattern/action 聲明, 其中的 patterns 被認為是并行的, 并且最終由 grammar 決定觸發哪個 action. 雖然默認的Perl 解析角度是從上至下的(或許使用一個中間層的從下至上角度來處理操作符優先級), 這對用戶理解 token 處理進行確定性很有用。所以, 為了 regex 匹配的意圖, 我們把 tokens 模式定義為那些不含潛在副作用或自引用的能被匹配的模式。(因為空格在行轉換時經常有副作用, 所以通常被這樣的模式排除, 給予或采取一點向前查看。) 基本上, Perl 自動地從 grammar 中派生出一個詞法分析程序, 而不需要你自己寫一個。
為此, Perl 6 中的每個 regex 被要求能把它的純模式和它的 actions 區分開, 并返回它的初始 token 模式的列表(包含由regex 的純部分調用的 subrule 的 token 模式, 但是不包含多于一次的 subrule, 因為那可能會引起自引用, 這在傳統正則表達式中是不被允許的。) 一個使用|
的邏輯備選分支接收兩個或多個這種列表并分發給匹配最長 token 前綴的備選分支。出現在第一位的可能是也可能不是那個備選分支。
然而, 如果兩個備選分支以同樣的長度匹配, 綁定首先由特異性打破。 以最長的固定字符串開頭的備選分支勝出; 即一個精確的匹配被看作是比使用字符類更接近. 如果它不起作用, 綁定會由兩個方法中的一個破壞. 如果備選分支在不同的 grammars 中, 那么標準的 MRO(方法解析順序)決定首先嘗試哪一個. 如果備選分支在同一個 grammar 文件中, 本文出現的更早的備選分支取得優先權. (如果一個 grammar 的 rules 被定義在不止一個文件中, 那么順序是未定義的, 則必須使用一個顯式的斷言用于強制失敗, 如果首先嘗試錯誤的那個的話)
這個長的標記前綴大致相當于“令牌”在其他分析系統使用一個詞法分析器的概念,但對于Perl這很大程度上是自動從語法定義一個偶然現象。然而,盡管是自動計算的,這一套標記可以由用戶修改;各種內構造正則表達式的語法來告訴引擎,這是完成圖案的部分開始的副作用,所以將這種構建用戶控件被認為是象征性的,什么是不。被視為終止一個令牌聲明并啟動“行動”部分的結構的結構包括:
這種最長 token 前綴大致相當于在其它解析系統中使用詞法解析程序的 "token" 標記, 但對于 Perl 這很大程度上是從 grammar 定義中派生的附帶現象。然而,盡管是自動計算的, 這套 tokens 可以由用戶修改; regex 中的各種結構聲明性的告訴 grammar 引擎, 模式部分結束, 并開始進入副作用, 所以通過插入這樣的結構, 用戶控制什么是 token, 什么不是。終止 token 聲明并開始模式的 "action" 部分的結構包括:
- 任何 :: 或 ::: 回溯控制 (而不是e : 肯定修飾符).
- 任何帶有節儉匹配(使用
?
修飾符)量詞化的原子。 - 任何
{...}
action, 但不是含有閉包的斷言。(空的閉包{}
通常用于顯式地終止模式的 pure 部分。) 一般的**{...}
量詞形式的閉包也會終止最長 token, 但是無閉包形式的量詞不會。
- 任何諸如
||
或&&
按次序的控制流操作符. - 作為前一點的結果,因為標準的 grammar 規則使用
||
定義空格, 最長的token 也由那 可能 使用那個規則匹配空格的 regex 或 rule 的任意部分終止, 包括通過:sigspace
隱式匹配的空格。(然而,token 聲明明確允許通過在 token 中使用諸如\h+
或其它字符類這種低級原語來識別空格) - Subpatterns(捕獲)不終止token模式,但可能需要重新解析 token以找到Subpatterns的位置。同樣地,在確定最長token之后斷言可能需要被檢查。(或者, 如果以任何一種方式模仿了 DFA 語義, 例如, 使用湯普森的NFA,可能可以知道什么時候觸發斷言而不使用backchecks。)
貪婪量詞和字符類不會終止 token 模式。 諸如單詞邊界的零寬斷言也不會。
因為這種斷言可以是 token 的一部分, 詞法分析程序引擎必須能從這種斷言的失敗中恢復, 并回溯到下一個最佳 token 候選者, 它可能等長或更短, 但是絕對不會當前候選者更長。
對于含有諸如 <?foo>
或 <?before \s>
這樣的正向向前查看的模式, 這種斷言會被認為比隨后的模式更特殊, 所以向前查看的模式被當作最長 token 的最后一部分; 最長 token 匹配器會足夠智能地把額外的 bit 當作是零寬的, 即, 重新匹配任何由向前查看遍歷到的文本,當它(如果)繼續匹配的時候。(實際上, 如果整個向前查看足夠純粹地參與 LTM, 再匹配可能僅僅優化掉 rematching, 因為向前查看已經在 LTM 引擎中匹配過了)
然而, 對于包含諸如 <!foo>
或 <!before \s>
這種否定向前查看斷言的模式, 反面的才是真: 隨后的模式被認為比該斷言更特殊。所以 LTM 完全忽略了否定向前查看, 并繼續從跟在否定向前查看后面的任何東西中查找純粹模式。你可能會說, 正向向前查看對 LTM 是不透明的, 否地向前查看對 LTM 是透明的。 結論是,如果你想寫一個對 LTM 是透明的正向向前查看, 你可以使用兩個感嘆號的否定: <!!foo>
來標示它。(優化器能自由地移除雙否定, 但是不是透明性)。
奇怪的是,這 令牌
關鍵詞具體不確定一個令牌的范圍,除了一個令牌模式通常不匹配的空白,而空白是終止令牌的典型方式。
很奇怪, token
關鍵字不確定 token 的作用域, 除了作為一個 token 模式通常不做很多的空格匹配情況之外, 空格是終止 tokens 的原型方式。
初始token匹配器必須把區分大小寫考慮在內(或任何其他規范化原語)并做正確的事, 即使傳播到不具有相同的規范化的 rules 時。也就是說,它們必須繼續代表較低規則能匹配的一組匹配。
||
形式有舊的短路語義,而不會試圖匹配其右側, 除非它的左側耗盡了所有的可能性(包括所有 |
可能性)。regex 中的第一個 ||
讓它左側的 token 模式能從外部的最長 token 匹配器中訪問, 但從最長 token 匹配隱藏的任何后續的測試。每一個 ||
建立了一個新的最長 token匹配器。那就是, 如果你在 ||
右側使用 |
,那么右側為最長 token 處理這子表達式和任何被調用的 subrules建立了一個新的頂級作用域處理這個子表達式和任何所謂的規則。右邊的最長 token 自動機是對于左側的 ||
或外部的含有 ||
的 regex是不可見的。
大西瓜啊,翻譯的狗屎一樣,慘不忍睹!