Rebol 程式設計/語言特性/解析/解析表示式
有時您需要解析一個序列,以檢視它是否與特定的格式匹配。這可用於簡單的事情,例如確定和驗證電話號碼或電子郵件地址的格式。
即使在拆分字串時,您也可能需要此功能,但不想將引號專門視為 NONE 或字串規則所做的那樣。(參見簡單拆分示例。)
PARSE 不使用正則表示式,而是匹配解析表示式。解析表示式
- 是 Rebol 的方言(解析方言)。
- 是自上而下解析語言(TDPL)家族中(增強過的)成員,包括自上而下解析語言(TDPL)、廣義自上而下解析語言(GTDPL)和解析表示式語法(PEG)。
- 使用與 TDPL 家族中其他成員相同的“有序選擇”解析方法。
- 透過有序選擇運算子,具有無限的預讀能力。
- 與 TDPL 家族中其他成員相容。
- 可以很好地替代正則表示式,因為它們的功能更強大。例如,正則表示式本質上不能找到匹配的括號對,因為它不是遞迴的,而 PARSE 方言可以。
- 的功能比上下文無關語法強大得多。
- 上下文無關語法通常需要一個單獨的詞法分析步驟,因為它們使用預讀的方式。PARSE 對字串型別的詞法分析不需要單獨的詞法分析步驟,並且詞法分析規則可以像其他語法規則一樣編寫。
- 許多上下文無關語法包含固有的歧義,即使它們旨在描述無歧義的語言。C、C++ 和 Java 中的“懸空 else”問題就是一個例子。這些問題通常透過在語法之外應用規則來解決。在 PARSE 方言中,由於優先順序,這些歧義永遠不會出現。
- 可以很好地替代 LL 解析器,因為它們的功能更強大。
PARSE 處理整個語言中使用的強大方言。例如,VID 或視覺介面方言用於構建圖形使用者介面。
PARSE 在解析表示式匹配期間所做的事情是遍歷一個序列(例如塊、字串或二進位制),並在此過程中,您可以執行操作或從序列中收集資訊,以在其他地方使用或對序列本身執行操作。
解析表示式匹配的用法主要有兩種方式:一種是對字串匹配字元模式,另一種是對塊匹配 Rebol 值的模式。這意味著
塊解析通常用於處理方言,這是該語言的主要特徵之一。
對於解析表示式匹配,給定的 RULE 引數必須是一個塊。塊的內容被解釋為起始解析表示式,對應於解析表示式語法中的起始表示式。
在匹配解析表示式時,PARSE 會維護輸入位置。
解析表示式匹配可能會有兩種結果
- 成功,在這種情況下,PARSE 可能會選擇向前移動輸入位置,或者
- 失敗,在這種情況下,輸入位置保持不變
如果滿足以下兩個條件,則 PARSE 返回 TRUE:
- 起始解析表示式被發現成功地匹配給定的 INPUT
- 最終的輸入位置位於給定 INPUT 的尾部
否則,PARSE 返回 FALSE。
原子解析表示式是隻由一個 Rebol 值表示的解析表示式。
NONE 被視為一個非終結符,它可以成功地匹配任何輸入,並且通常不會向前移動輸入位置,在字元集部分提到了一個例外。
parse "" [#[none]] ; == true
這將返回 TRUE,因為
- NONE 值可以成功地匹配任何輸入。
- PARSE 已經位於字串的尾部。
parse [] [#[none]] ; == true
注意
在解析字串以及解析塊時,字元都被視為終結符。
parse "a" [#"a"] ; == true
這將返回 TRUE,因為
- #"a" 字元可以成功地匹配當前輸入位置的字元。
- PARSE 在成功匹配後向前移動,在這種情況下,它到達了輸入的末尾。
parse [#"a"] [#"a"] ; == true
解析字串時,任何字串都被視為終端符號序列
parse "aaa" ["aaa"] ; == true
這將返回 TRUE,因為
- 該字串成功匹配了當前輸入位置處的輸入的一部分。
- PARSE 在成功匹配後向前移動,在這種情況下,它到達了輸入的末尾。
parse "<html>" [<html>] ; == true
解析塊時,任何字串都被視為終端符號
parse ["aaa"] ["aaa"] ; == true
parse [<html>] [<html>] ; == true
塊被視為非終結符;它們的內容被解釋為解析表示式並用於匹配。
parse "a" [["a"]] ; == true
parse ["a"] [["a"]] ; == true
注意
括號成功匹配任何不前進 PARSE 位置的輸入;它們被視為動作以供評估,這會導致在下面的示例中將字串“OK”列印到控制檯
parse "" [(print "OK")] ; == true
parse [] [(print "OK")] ; == true
注意
不需要任何引數的 PARSE 關鍵字被視為非終結符.
parse "" [end] ; == true
這將返回 TRUE,因為
- 輸入已在尾部。
- 當輸入位於尾部時,end 關鍵字成功匹配輸入。
parse "a" [skip] ; == true
這將返回 TRUE,因為
- skip 關鍵字成功匹配任何終端.
- PARSE 在成功匹配後向前移動,在這種情況下,它到達了輸入的末尾。
parse ["aa"] [skip] ; == true
注意
不是 PARSE 關鍵字的 Rebol 單詞被視為非終結符。查詢此類單詞的值並將其用於匹配。
parse "" [none] ; == true
這將返回 TRUE,因為
- 'none 變數引用 NONE 值,該值成功匹配任何輸入。
- PARSE 已經位於輸入的末尾。
注意
- 有關在輸入中匹配特定單詞的資訊,請參見Lit-word 部分。
- 引用 PARSE 關鍵字的單詞按關鍵字處理。
- 不支援引用其他單詞的單詞。當 PARSE 遇到此類單詞時,它會導致“無效引數”錯誤。
Lit-word 只能在塊解析期間使用。每個 lit-word 被視為非終結符,它成功匹配當前輸入位置處的對應單詞。
parse [Hi] ['Hi] ; == true
不同的單詞匹配將失敗
parse [Bye] ['Hi] ; == false
注意
路徑與單詞類似,即查詢路徑的值並將其用於匹配。
Lit-path 與 lit-word 類似,即它們匹配輸入塊中的對應路徑。
注意
解析字串時,位集作為字元集 非終結符。它們成功匹配它們包含的任何字元。
whitespace: charset [#"^A" - #" " #"^(7F)" #"^(A0)"] parse/all " " [whitespace] ; == true
請注意,我們透過使用 /ALL 精煉“關閉”了空格字元的特殊處理。
瞭解一下如果我們不“關閉”空格字元的特殊處理會發生什麼,可能很有趣
whitespace: charset [#"^A" - #" " #"^(7F)" #"^(A0)"] parse " " [whitespace] ; == false
結果為 FALSE,因為 PARSE 在這種情況下“忽略”空格字元,因此無法成功匹配它們。
下一個嘗試也不成功
parse " " [] ; == false
,因為輸入位置尚未位於尾部。
要成功,我們需要執行
parse " " [none] ; == true
,其中 NONE 值用於匹配空格並向前移動 PARSE 輸入位置。
注意
- 上述 NONE 行為是一個特例,即使 NONE 值也可以向前移動當前輸入位置。
- 在塊解析的情況下,位集的行為像終端符號.
解析塊時,我們可以使用 Rebol 資料型別作為非終結符。它們成功匹配任何對應資料型別的值。
parse [5] [integer!] == true
這將返回 TRUE,因為
- 'integer! 單詞引用的 INTEGER! 資料型別成功匹配了塊中的元素。
- PARSE 在向前移動後到達了塊的末尾。
同樣的事情,只是使用日期和字串
parse [25-Dec-2005] [date!] ; == true
parse ["Hello"] [string!] ; == true
NONE! 資料型別可以用於匹配輸入中的 NONE 值
parse [#[none]] [none!] ; == true
注意
- 由於資料型別被視為 非終結符,因此它們不能用作 終結符 來匹配輸入塊中的資料型別。要匹配輸入中的特定資料型別,請參閱 解析習語 部分中的引號習語。
- INTEGER! 資料型別在字串解析期間用於匹配整數表示形式。其他資料型別“工作”為 NONE。
設定詞
[edit | edit source]設定詞被視為 非終結符,用於獲取當前輸入位置。設定詞始終成功,不會移動輸入位置。
parse "123" [position:] ; == false position ; == "123"
解釋
- PARSE 將“位置變數”設定為引用當前輸入位置
- 匹配成功,不向前移動輸入位置
- PARSE 返回 FALSE,因為最終的輸入位置沒有到達輸入的尾部
注意
獲取詞
[edit | edit source]獲取詞被視為 非終結符,用於設定當前輸入位置。嘗試將輸入位置設定為完全不同的序列(一個與當前輸入位置沒有相同頭部的序列)會導致錯誤。否則匹配成功。示例
string: tail "123" parse head string [:string] ; == true
解釋
- 匹配成功
- PARSE 返回 TRUE,因為在這種情況下,位置被設定為輸入的尾部。
注意
本地
[edit | edit source]本地人在塊解析期間匹配什麼?
其他資料型別
[edit | edit source]塊解析期間可以使用除上述提到的其他資料型別之外的其他資料型別的值作為 終結符。
解析操作
[edit | edit source]讓我們看看 PARSE 如何遍歷一個塊
parse [Hi Bye] [word!] ; == false
這裡出了什麼問題?發生的事情是 PARSE 成功匹配了 INPUT 塊中的第一個詞,並將輸入推進到第二個詞。
該塊尚未解析到結尾,這意味著 PARSE 返回 FALSE。
為了在更復雜的情況下匹配輸入,除了上面提到的原子表示式之外,我們還需要解析操作。
序列
[edit | edit source]序列操作是解析表示式的序列。它不使用關鍵字。序列的一般形式為
subexpression_1 subexpression_2 ... subexpression_n
在匹配序列時,PARSE 匹配子表示式_1,如果成功,則嘗試匹配序列的其餘部分。為了使序列匹配成功,所有子表示式匹配都需要成功。
示例
parse [Hi Bye] ['Hi word!] ; == true
在這種情況下,序列匹配成功,輸入被推進到其尾部,導致 PARSE 返回 TRUE。
有序選擇
[edit | edit source]有序選擇(又稱“備選”,但“有序選擇”這個名字更合適,因為子表示式的順序很重要)操作使用|關鍵字。此操作的一般形式為
subexpression_1 | subexpression_2 | ... | subexpression_n
在匹配有序選擇時,PARSE 嘗試匹配子表示式_1。如果成功,則有序選擇匹配成功。如果第一個子表示式匹配不成功,PARSE 嘗試匹配選擇的其餘部分。
有序選擇操作的優先順序低於序列操作,這意味著
e1 e2 | e3
等效於
[e1 e2] | e3
假設你想檢查塊元素是整數還是小數
parse [36] [integer! | decimal!] ; == true
parse [37.2] [integer! | decimal!] ; == true
注意:有序選擇操作的優先順序導致|中的第二個子表示式
["a" | "ab"]
選擇永遠不會成功,因為第一個子表示式具有優先順序。
重複運算子
[edit | edit source]重複運算子指定給定子表示式應該匹配多少次。重複的一般語法為
repetition_operator subexpression
重複運算子的優先順序高於序列運算子,這意味著
repetition_operator subexpression_1 subexpression_2
與以下含義相同
[repetition_operator subexpression_1] subexpression_2
所有重複運算子都是貪婪的,這意味著它們總是儘可能多地匹配。
重複運算子是左結合的,這意味著
any 2 skip
等效於
[any 2] skip
對於to、thru 和into 運算子也是如此。
零或一
[edit | edit source]此運算子使用opt關鍵字。它也被稱為可選匹配。由於允許零計數,因此此運算子始終成功。
示例
parse "," [opt #","] ; == true
parse "" [opt #","] ; == true
一或更多
[edit | edit source]此運算子使用some關鍵字。
示例
parse "," [some #","] ; == true
parse ",," [some #","] ; == true
零或更多
[edit | edit source]此運算子使用any關鍵字。由於允許零計數,因此此運算子始終成功。
示例
parse ",," [any #","] ; == true
parse "" [any #","] ; == true
parse [Hi Bye] [any word!] ; == true
它返回 TRUE,因為
- any 運算子始終成功
- any 運算子是貪婪的,成功匹配了輸入中的所有詞,將最終的輸入位置留在了尾部
如果我們在塊中新增不同的資料型別
parse [Hi 36 Bye] [any word!] ; == false
PARSE 返回 FALSE,因為
- any 運算子成功匹配了輸入的第一個元素,並停止,沒有到達尾部
重複次數
[edit | edit source]此操作的一般形式為
n subexpression
其中 N 是一個整數。此操作指定了給定子表示式的重複次數。
示例
parse "12" [2 skip] ; == true
該表示式檢查是否有兩個詞,不多不少
parse [Hi Bye] [2 word!] ; == true
次數範圍
[edit | edit source]此操作的一般形式為
n m subexpression
其中 N 和 M 是整數。此操作指定了子表示式的重複範圍。
示例
parse "12" [1 2 skip] ; == true
parse [Hi Bye] [1 2 word!] ; == true
該表示式檢查不小於 1 個詞也不大於 2 個詞。
parse [Hi how are you? Bye] [0 5 word!] ; == true
此表示式將成功匹配 0 到 5 個詞。
請注意,在解析整數時,我們必須指定一個範圍,因為整數用於指定匹配範圍。
這指定了正好一個匹配
parse [-1] [1 1 -1] ; == true
這是一個錯誤
parse [-1] [-1] ; == false
有兩個 PARSE 運算子根據給定的解析子表示式推進輸入位置
- to 運算子
- thru 運算子
操作的一般語法是
to subexpression
或
thru subexpression
to 運算子的目的是推進輸入位置直到成功子表示式匹配發生的位置。
thru 運算子的目的是將輸入位置推進到成功子表示式匹配之後的位置。
如果找不到成功的子表示式匹配,則這兩個操作都會失敗。
子表示式可以是
- end 關鍵字。該
to end
- 操作始終成功地將輸入推進到其尾部,而該
thru end
- 操作在 R2 中始終失敗;在更新的 R3 版本中,它已經得到改進,並與解析習語部分中提到的遞迴習慣用法相容
- 一個單詞 - 在這種情況下,它的值將被查詢並用於匹配
解析字串時支援以下子表示式
- 字元
- 字串
解析塊時支援以下子表示式
如果您要解析大量資料並且不關心塊中的某些內容,這非常有用。
假設我們不關心任何內容,直到我們找到一個單詞。可以使用to 來實現這一點
parse [37.2 38 Bye] [to word!] ; == false
這使得 PARSE 返回 FALSE,因為
- 我們已經成功找到一個單詞,並且to 操作成功,但是
- 我們還沒有到達輸入的尾部。
為了到達輸入的尾部,我們可以使用thru 而不是to 來繼續經過該單詞。
parse [37.2 38 Bye] [thru word!] ; == true
解析塊時,您可能需要檢查其子塊(或括號、路徑、文字路徑或獲取路徑)以查詢特定模式。into 運算子適用於此。操作的一般形式是
into subexpression
僅接受塊或引用塊的單詞作為子表示式。
如果當前輸入位置處的元素不具有 ANY-BLOCK! 型別,則子塊解析操作將失敗。否則,將使用該元素(子塊)作為輸入,並與給定的子表示式進行匹配。為了使子塊解析成功,子塊必須成功匹配給定的子表示式,並且最終的子塊輸入位置必須是子塊的尾部。
示例
parse [[]] [into [none]] ; == true parse [[1]] [into [none]] ; == false parse [(1)] [into [skip]] ; == true parse [a/b] [into [2 skip]] ; == true parse ['a/b] [into ['a 'b]] ; == true
您還可以使用序列中的資料在觸發程式碼中使用。除了使用設定詞獲取輸入位置之外,還有以下 PARSE 操作
set variable subexpression
copy variable subexpression
set 操作僅在塊解析期間可用,而copy 操作在兩種解析模式下都可用。如果子表示式匹配成功,則set 操作將給定變數設定為第一個匹配的值,而copy 操作則複製由給定子表示式匹配的輸入的整個部分。有關更詳細的說明,請參見解析習語部分。
parse [Hi 36 37.2 38 Bye] [ word! any [set int integer! (print ["Integer" int "Found"]) | decimal! (print "Decimal Found")] word! ] ; Integer 36 Found ; Decimal Found ; Integer 38 Found ; == true
我們可以將 Series 的普通函式應用於從我們正在解析的序列中提取資料。
parse [Hi 36 37.2 38 Bye] [
any [
set int integer! (print ["Integer" int "Found"])
| dec: decimal! (print ["Decimal Found at position" index? dec])
| wrd: thru word! (print ["Word" first wrd "is near tail:" tail? wrd])
]
]
; Word Hi is near tail: false
; Integer 36 Found
; Decimal Found at Position 3
; Integer 38 Found
; Word Bye is near tail: true
; == true
這將複製字串的一部分
parse "123456" [copy part 5 skip to end] ; == true part ; == "12345"
fail - R3 parse 的新關鍵字,一個不匹配任何輸入的非終結符
quote 值 - R3 parse 的新關鍵字,按原樣匹配該值
if(表示式) - R3 parse 的新關鍵字,在括號中計算表示式,如果結果是false 或 none,則將其視為匹配失敗
into 子規則 - 與 R2 中一樣,但在 R3 中,子規則匹配的專案可以與輸入具有不同的資料型別
return 值 - R3 parse 的新關鍵字,立即從parse 中返回給定值
and 子規則 - R3 parse 的新關鍵字;前瞻規則;匹配子規則但不推進輸入
not 子規則 - R3 parse 的新關鍵字,反轉匹配子規則的結果
?? - R3 parse 的新關鍵字,列印除錯輸出
then 子規則 - R3 parse 的新關鍵字,無論子規則匹配是否成功,都跳過下一個備選方案
any 子規則 - 為了防止 R3 中出現不必要的無限迴圈,此規則在子規則匹配輸入但未推進時也會停止
some 子規則 - 為了防止 R3 中出現不必要的無限迴圈,此規則在子規則匹配輸入但未推進時也會停止
while 子規則 - R3 parse 的新關鍵字,在子規則匹配輸入時迭代子規則匹配;此規則類似於any,但具有更簡單的停止條件(以迴圈可能變成無限迴圈為代價)
accept - R3 parse 的新關鍵字,與break 的作用完全相同
reject - R3 parse 的新關鍵字,停止匹配迴圈並指示迴圈匹配失敗
to - 現在允許多個目標,但仍然不夠通用,無法允許任何目標規則
thru - 現在允許多個目標,但仍然不夠通用,無法允許任何目標規則
change 子規則only 值 - R3 parse 的新關鍵字,更改匹配子規則的輸入部分(警告!這非常慢!此外,輸入更改會損害程式碼的可理解性!由於這些原因,使用它不是一個好的程式設計實踐。)
insert - R3 parse 的新關鍵字,(警告!這非常慢!此外,輸入更改會損害程式碼的可理解性!由於這些原因,使用它不是一個好的程式設計實踐。)
remove 子規則 - R3 parse 的新關鍵字,(警告!這非常慢!此外,輸入更改會損害程式碼的可理解性!由於這些原因,使用它不是一個好的程式設計實踐。)
do 子規則 - Gabriele 提出的 R3 parse 的新關鍵字
可以構建複雜的解析表示式。這樣做時,將它們分解成更小的部分併為它們賦予有意義的名稱會很有用。
遞迴通常是描述語法的最優雅方式。以包含 anbn 型別字串的語法為例,其中 n >= 1。可以使用以下解析表示式來描述這種語法
anbn: ["a" anbn "b" | "ab"]
用法
parse "ab" anbn ; == true parse "aabb" anbn ; == true
| 說明 | 操作 | 習語 |
|---|---|---|
| Any-string,字串解析 | a: ["abc"] | a: [#"a" #"b" #"c"][1] |
| Bitset,字串解析 | a: charset ",;" | a: [#"," | #";"][2] |
| skip 非終結符,字串解析 | a: [skip] | b: 補充字元集 ""[3] a: [b][4] |
| 跳過 非終結符,塊解析 | a: [skip] | a: [任何型別!][5] |
| 可選 運算子(零或一) | a: [可選 b] | a: [b |][6][7] |
| 任何 運算子(零或多個)[8] | a: [任何 b] | a: [b a |][9][10] |
| 某些 運算子(一個或多個)[8] | a: [某些 b] | a: [b [a |]][11][12] |
| 失敗(始終失敗的非終結符) | a: [失敗] | a: [某些 "a" "a"][13] a: [結束 跳過][14] |
| 時間範圍 運算子 | a: [m n b] | a: [(l: 最小 m n k: n - m) l b [k [b | c: 失敗] | :c]][15][16] |
| 然後 運算子[17] (匹配 B 並且如果成功,匹配 C;否則匹配 D) |
a: [b 然後 c | d] | a: [[b (e: c) | (e: d)] e][18] |
| 非 謂詞[19](反轉成功) | a: [非 b] | a: [b 然後 失敗 |][20] a: [[b (c: [失敗]) | (c: 無)] c] |
| 並且 謂詞(匹配但不前進) | a: [並且 b] | a: [c: b :c][21] a: [非 非 b][22] a: [[b (c: 無) | (c: [失敗])] 失敗 | c][23][24] |
| 結束 非終結符(匹配輸入的尾部) | a: [結束] | a: [非 跳過][25][26] |
| 開始 非終結符(匹配輸入的頭部)[27] | a: [開始] | a: [b: (c: 除非 頭部? b [[失敗]]) c][28][29] |
| 到 運算子[8] (前進到第一個成功匹配) |
a: [到 b] | a: [並且 b | 跳過 a][30][31][32] a: [任何 [非 [b (c: 無)] (c: [失敗]) 跳過] c][33] a: [任何 [[b (c: 無 d: [失敗]) | (c: [失敗] d: [跳過])] d] c][34] a: [穿過 [並且 b]][35] |
| 到 和 任何 之間的對應關係 | a: [到 [b | 結束]][36] | a: [任何 [非 b 跳過]] |
| 穿過 運算子[8] (前進穿過第一個成功匹配) |
a: [穿過 b] | a: [b | 跳過 a][37][38][39] a: [任何 [非 [b c: (d: [:c])] (d: [失敗]) 跳過] d][33] a: [任何 [[b c: (d: [:c] e: [失敗]) | (d: [失敗] e: [跳過])] e] d][34] a: [到 [b c:] :c][35] |
| 設定 運算子 (將變數設定為第一個匹配的值) |
a: [設定 b c] | f: [(設定/任何 [b] 如果 小於? 索引? e 索引? d [e])][40][41][42] a: [並且 [c d:] e: f :d][43] |
| 複製 運算子 (將變數設定為匹配的序列) |
a: [複製 b c] | f: [(b: 如果 小於? 索引? e 索引? d [複製/部分 e d])][44][42] a: [並且 [c d:] e: f :d][43] |
| 引用 運算子,塊解析(匹配終結符) | a: [引用 b] | a: [複製 c 跳過 (d: 除非 等於? c [b] [[失敗]]) d][45][46] |
表格說明了
- ↑ 在解析字串時,字串作為字元序列工作
- ↑ 在解析字串時,位集作為字元選擇工作
- ↑ 包含所有字元的位集可以定義為不包含任何字元的位集的補集
- ↑ 在解析字串時,跳過 非終結符作為包含所有字元的位集工作
- ↑ 在解析塊時,跳過 非終結符作為ANY-TYPE! 資料型別工作
- ↑ 可選 運算子可以使用通用選擇來定義
- ↑ 可選 是貪婪的,因為第一個選擇子表示式具有優先順序
- ↑ a b c d 一個迭代運算子替換常見的遞迴非終結符:
- 增強表達能力(節省非終結符定義)
- 最佳化記憶體使用(節省堆疊空間)
- 最佳化速度
- ↑ 任何 運算子可以使用通用遞迴表示式來定義
- ↑ 任何 是貪婪的,因為第一個選擇子表示式具有優先順序
- ↑ 某些 運算子可以使用通用遞迴表示式來定義
- ↑ 某些 是貪婪的,因為第一個選擇子表示式具有優先順序
- ↑ 失敗 非終結符可以定義(即使沒有結束 關鍵字!)使用某些的貪婪性
- ↑ 失敗 版本使用結束和跳過關鍵字更簡潔,儘管
- ↑ 時間範圍 運算子可以使用重複序列來定義
- ↑ 時間範圍 運算子是貪婪的,因為第一個選擇子表示式具有優先順序
- ↑ 然後 運算子,增強了明顯的表達能力,用於廣義 TDPL
- ↑ 然後 運算子可以使用選擇和計算的非終結符來定義
- ↑ "謂詞" 意味著它不會推進輸入位置
- ↑ 非 謂詞可以使用然後運算子和失敗非終結符來定義
- ↑ 並且 謂詞可以使用位置操作來定義(警告:這不是遞迴安全的;非終結符 B 不得更改變數 'c 的值!)
- ↑ 並且 謂詞可以使用非謂詞來定義
- ↑ 並且 謂詞可以使用選擇、序列和計算的非終結符來定義
- ↑ 解釋:主選擇的第一個子表示式是一個序列,它被設計為始終失敗計算一個非前進的非終結符 C,以便 C 成功,如果 B 成功
- ↑ 結束 非終結符可以使用非謂詞來定義(注意,這不是迴圈定義!)
- ↑ 這個習語很好地解釋了為什麼 [結束 跳過] 習語總是失敗
- ↑ 一些使用者建議檢測輸入序列何時處於其頭部可能很有用
- ↑ 開始 非終結符可以使用序列和計算的非終結符來定義
- ↑ 解釋:除非輸入位置位於輸入序列的開頭,否則 C 會計算為失敗。
- ↑ to 運算子可以使用and 運算子和一個常見的遞迴表示式來定義。
- ↑ 遞迴定義比當前的to 運算子更通用,支援任何非終結符 B。
- ↑ 遞迴定義與to 運算子的行為相同,除了:
- [to ""] 表示式在解析字串時始終失敗,而遞迴表示式始終成功
- ↑ a b 這是一個使用any、not、fail、skip 和計算出的非終結符的運算子的等效迭代定義。
- ↑ a b 這是一個等效的迭代定義,擴充套件了not 習慣用法。
- ↑ a b 這展示了to 和thru 運算子之間的關係。
- ↑ | END 選擇會導致to 運算子始終成功。
- ↑ thru 運算子可以使用一個常見的遞迴表示式來定義。
- ↑ 遞迴定義比當前的thru 運算子更通用,支援任何非終結符 B。
- ↑ 遞迴定義與thru 運算子的行為相同,除了:
- [thru end] 表示式始終失敗,而遞迴表示式始終成功
- [thru ""] 表示式在解析字串時始終失敗,而遞迴表示式始終成功
- ↑ set 運算子可以使用and 運算子、位置操作、動作和序列來定義。
- ↑ 請注意,此set 定義不限於塊解析。
- ↑ a b 解釋:使用 D 和 E 根據需要設定 'b 變數。請注意,如果輸入位置沒有向前移動,則 'b 必須設定為 NONE。
- ↑ a b 解釋:序列中的第一個子表示式被定義,以便我們知道 B 匹配後的位置(用於設定 'd)和之前的位置(用於設定 'e)。
- ↑ copy 運算子可以使用and 運算子、位置操作、動作和序列來定義。
- ↑ quote 習慣用法使用示例:a: [copy c skip (d: unless equal? c ['hi] [[fail]]) d] 匹配字面量 'hi。
- ↑ 解釋:除非輸入的第一個元素等於給定的終結符,否則 C 會計算為失敗。
如果你想使用上面的一些習慣用法,你不需要記住它們。相反,你可以使用parseen 指令碼,在那裡你可以找到為你生成對應規則的函式。
解析規則中的區域性變數
[edit | edit source]有時在解析規則中使用區域性變數是可取的。這樣的變數至少需要是遞迴安全的,因為解析規則經常被遞迴使用,但將來可能還需要執行緒安全的區域性變數。PARSE 函式沒有內建支援這種結構,但是,由於 Rebol 的可塑性,可以定義一個USE-RULE 函式,它方便在 PARSE 規則中使用(遞迴和執行緒安全)區域性變數,其工作原理如下。
rule: use-rule [a b] [
"b" (print 1) |
a: "a" rule "a" b: (print subtract index? b index? a)
]
parse "aba" rule
parse "aabaa" rule
在 PARSE 規則中使用區域性變數的更復雜示例是evaluate.r 指令碼,它展示瞭如何在 PARSE 中處理不同的優先順序和結合性規則集。
修改輸入序列
[edit | edit source]在表示式匹配期間可以操作 PARSE 輸入序列,因為可以在解析操作期間使用諸如 CHANGE、INSERT 或 REMOVE 之類的序列操作函式。
以下是一些不建議在表示式匹配期間操作輸入序列的原因。
- 與其更改輸入序列,不如使用一個新的序列並按照需要收集其內容。
- 某些操作會改變當前解析的序列的長度。更改長度的操作效率低下 - 更改長度的操作需要 O(N) 時間,其中 N 是被更改序列的長度。將此與用於收集方法的 APPEND 操作進行對比,APPEND 操作的速度大約快 N 倍。
- 更改長度的操作會破壞輸入位置的簿記。這樣很容易產生難以理解和除錯的程式碼。
示例
讓我們定義一個測試字串
n: 100 random/seed 0 test-string: copy "" repeat i n [insert tail test-string pick "abc" random 3]
讓我們使用 PARSE 實現一個 remove-chars 函式。第一次嘗試“就地”更改序列,但更改不會影響輸入序列的長度。
remove-chars1: func [
{Removes the given chars from a string.}
string [string!]
chars [char! string!] "All characters specified will be removed."
/local chars-to-keep group change-position
] [
; if a char, use a string instead
if char? chars [chars: head insert copy "" chars]
; define the characters we want to keep:
chars: charset chars
chars-to-keep: complement chars
; the position where the change needs to occur
change-position: string
; turn off the default whitespace handling
parse/all string [
any [
; ignore chars
any chars
; get a group of chars-to-keep
copy group some chars-to-keep
(change-position: change change-position group)
]
]
clear change-position
string
]
第二次嘗試使用 REMOVE 函式更改輸入序列的長度。
remove-chars2: func [
{Removes the given chars from a string.}
string [string!]
chars [char! string!] "All characters specified will be removed."
/local chars-to-keep group-start group-end
] [
; if a char, use a string instead
if char? chars [chars: head insert copy "" chars]
; define the characters we want to keep:
chars: charset chars
chars-to-keep: complement chars
; turn off the default whitespace handling
parse/all string [
any [
; ignore chars-to-keep
any chars-to-keep
; remove group of chars
group-start: some chars group-end:
(remove/part group-start group-end)
]
]
string
]
結果是
r1: remove-chars1 copy test-string "a"
; == {cbccbbcccbbbbbcccccccbcbbcbcbcbbccbcbbccccbcbbcbbcbbcbccccbcbb}
r2: remove-chars2 copy test-string "a"
; == {cbccbbcccbababbbcccaccccbcbbaacbcbcbbccbcbbccccbcbbcbbcbbcbccccbacbb}
令人驚訝的是!使用輸入操作的方法沒有按預期刪除所有 #"a"!問題是由不正確的輸入位置處理造成的。因此,讓我們固執一些,正確地處理輸入位置。
remove-chars3: func [
{Removes the given chars from a string.}
string [string!]
chars [char! string!] "All characters specified will be removed."
/local chars-to-keep group-start group-end
] [
; if a char, use a string instead
if char? chars [chars: head insert copy "" chars]
; define the characters we want to keep:
chars: charset chars
chars-to-keep: complement chars
; turn off the default whitespace handling
parse/all string [
any [
; ignore chars-to-keep
any chars-to-keep
; remove chars
group-start: some chars group-end:
(remove/part group-start group-end)
; set the input position properly
:group-start
]
]
string
]
結果是
r3: remove-chars3 copy test-string "a"
; == {cbccbbcccbbbbbcccccccbcbbcbcbcbbccbcbbccccbcbbcbbcbbcbccccbcbb}
速度討論
[edit | edit source]由於 CHANGE 函式(或任何其他 Rebol 原生函式)不允許在不復制的情況下移動序列的一部分,因此 REMOVE-CHARS1 函式必須做很多額外的工作,分配和收集很多組字串,而 REMOVE-CHARS3 函式使用 REMOVE 原生的全部速度,不需要分配或釋放任何額外的“輔助”字串。
證明 REMOVE-CHARS1 函式演算法質量的一個證明是,即使在這樣不利的條件下,它的速度仍然具有競爭力,即使對於最短的字串也是如此,並且對於短至 600 個字元的字串,REMOVE-CHARS1 函式變得比 REMOVE-CHARS3 函式更快。對於更長的字串,速度差異將更加有利於 REMOVE-CHARS1 函式。
故障排除
[edit | edit source]PARSE 是一個非常強大的函式,但如果你沒有密切關注你在做什麼,它也可能很麻煩。如果你不幸的話,PARSE 會陷入無限迴圈,需要你重新啟動 Rebol。
但它究竟是什麼時候發生的?
PARSE 通常會遍歷塊,但真正使 PARSE 進步的是表示式。如果你指定一個不會使其進步的表示式,它將永遠停留在同一個點。
這樣的表示式可以是重複匹配空序列、空選擇子表示式或 NONE 表示式的表示式。
示例
>> parse "abc" [any []] *** HANGS Rebol ***
>> parse "abc" [some ["a" |]] *** HANGS Rebol ***
>> parse "abc" [some [none]] *** HANGS Rebol ***
注意:為了能夠從無限迴圈中逃脫,請在迭代表達式中使用 (),例如
>> parse "abc" [any [()]] *** YOU CAN PRESS [ESC] NOW TO STOP THE LOOP ***