跳至內容

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

注意

  • 由於 Rebol 程式碼塊被視為非終結符,因此它們不能用作終結符 來逐字匹配輸入中包含的特定程式碼塊。要匹配特定程式碼塊,您可以使用 into 運算子或在解析習慣用法 部分中定義的 quote 慣用法。

Paren

[edit | edit source]

括號成功匹配任何不推進解析位置的輸入;它們被視為要評估的操作,這將導致在下面的示例中將字串“OK”列印到控制檯

parse "" [(print "OK")]
; == true
parse [] [(print "OK")]
; == true

注意

  • 由於括號被視為操作,因此它們不能用作終結符 來匹配輸入程式碼塊中的括號。如果要匹配特定的括號,可以使用 into 運算子或在解析習慣用法 部分中使用 quote 慣用法。

無引數的 PARSE 關鍵字

[edit | edit source]

不需要任何引數的 PARSE 關鍵字被視為非終結符

end 關鍵字

[edit | edit source]
parse "" [end]
; == true

這將返回 TRUE,因為

  • 輸入已處於其尾部。
  • 當輸入處於其尾部時,end 關鍵字成功匹配輸入。

skip 關鍵字

[edit | edit source]
parse "a" [skip]
; == true

這將返回 TRUE,因為

  • skip 關鍵字成功匹配任何終結符
  • PARSE 在成功匹配後向前移動,在本例中,它到達了輸入的尾部。
parse ["aa"] [skip]
; == true

注意

  • PARSE 關鍵字不能用作終結符 來匹配輸入程式碼塊中的特定單詞。有關匹配此類單詞,請參見Lit-word 部分。

不是 PARSE 關鍵字的 Rebol 單詞被視為非終結符。查詢並使用此類單詞的值進行匹配。

parse "" [none]
; == true

這將返回 TRUE,因為

  • 'none 變數引用 NONE 值,該值成功匹配任何輸入。
  • PARSE 已經位於輸入的尾部。

注意

  • 有關在輸入中匹配特定單詞,請參見Lit-word 部分。
  • 引用 PARSE 關鍵字的單詞作為關鍵字處理。
  • 不支援引用其他單詞的單詞。當 PARSE 遇到此類單詞時,它會導致“無效引數”錯誤。

Lit-word

[edit | edit source]

Lit-word 只能在程式碼塊解析期間使用。每個 lit-word 被視為一個非終結符,它成功匹配當前輸入位置的相應單詞。

parse [Hi] ['Hi]
; == true

不同的單詞匹配將失敗

parse [Bye] ['Hi]
; == false

注意

  • 由於 lit-word 是非終結符,因此它們不能用作終結符 來匹配輸入程式碼塊中的 lit-word。有關匹配特定的 lit-word,請參見解析習慣用法 部分中的 quote 慣用法。

路徑的工作方式類似於單詞,即查詢並使用路徑的值進行匹配。

Lit-path

[edit | edit source]

Lit-path 的工作方式類似於 lit-word,即它們匹配輸入程式碼塊中的相應路徑。

注意

  • 由於 lit-path 是非終結符,因此它們不能用作終結符 來匹配輸入程式碼塊中的 lit-path。有關匹配特定的 lit-path,請參見解析習慣用法 部分中的 quote 慣用法。

字元集

[edit | edit source]

在解析字串時,位集作為字元集非終結符。它們成功匹配它們包含的任何字元。

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 值也可以將當前輸入位置向前移動。
  • 在程式碼塊解析的情況下,位集的行為類似於終結符

Datatype

[edit | edit source]

在解析程式碼塊時,我們可以使用 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

注意

  • 由於資料型別被視為非終結符,因此它們不能用作終結符 來匹配輸入程式碼塊中的資料型別。要匹配輸入中的特定資料型別,請參見解析習慣用法 部分中的 quote 慣用法。
  • INTEGER! 資料型別在字串解析期間匹配整數表示。其他資料型別“按”NONE 工作。

Set-word

[edit | edit source]

Set-word 被視為非終結符,並用於獲取當前輸入位置。Set-word 始終成功,不移動輸入位置

parse "123" [position:]
; == false
position
; == "123"

說明

  • PARSE 將 'position 變數設定為引用當前輸入位置
  • 匹配成功,不將輸入位置向前移動
  • PARSE 返回 FALSE,因為最終輸入位置沒有到達輸入的尾部

注意

  • 由於 set-word 被視為非終結符,因此它們不能用作終結符 來匹配輸入程式碼塊中的特定 set-word。要匹配特定的 set-word,請參見解析習慣用法 部分中的 quote 慣用法。

Get-words 被視為非終結符,並用於設定當前的輸入位置。嘗試將輸入位置設定為完全不同的系列(一個與當前輸入位置不具有相同頭的系列)會導致錯誤。否則匹配成功。示例

string: tail "123"
parse head string [:string]
; == true

說明

  • 匹配成功
  • PARSE 返回 TRUE,因為在這種情況下,位置被設定為輸入的尾部。

注意

  • 由於 get-words 被視為非終結符,它們不能用作終結符來匹配輸入塊中的特定 get-words。要匹配特定的 get-words,請參見解析習語部分中的引號習語。

本機在塊解析期間匹配什麼?

其他資料型別

[編輯 | 編輯原始碼]

塊解析期間可以使用除上述資料型別以外的其他資料型別的值作為終結符

解析操作

[編輯 | 編輯原始碼]

讓我們看看 PARSE 如何遍歷塊

parse [Hi Bye] [word!]
; == false

這裡哪裡出錯了?發生的情況是,PARSE 成功地匹配了 INPUT 塊中的第一個詞,並將輸入前進到第二個詞。

塊尚未解析到末尾,這意味著 PARSE 返回 FALSE。

為了在更復雜的情況下匹配輸入,除了上面提到的原子表示式之外,我們還需要解析操作

序列操作解析表示式的序列。它不使用關鍵字。序列的一般形式是

subexpression_1 subexpression_2 ... subexpression_n

在匹配序列時,PARSE 匹配 subexpression_1,如果成功,則嘗試匹配序列的其餘部分。為了使序列匹配成功,需要所有 subexpression 匹配都成功。

示例

parse [Hi Bye] ['Hi word!]
; == true

在這種情況下,序列匹配成功,輸入被前進到其尾部,這導致 PARSE 返回 TRUE。

有序選擇

[編輯 | 編輯原始碼]

有序選擇(也稱為“備選”,但“有序選擇”名稱更合適,因為子表示式的順序很重要)操作使用|關鍵字。此操作的一般形式是

subexpression_1 | subexpression_2 | ... | subexpression_n

在匹配有序選擇時,PARSE 嘗試匹配 subexpression_1。如果成功,則有序選擇匹配成功。如果第一個 subexpression 匹配不成功,則 PARSE 嘗試匹配選擇中的其餘部分。

有序選擇操作的優先順序低於序列操作,這意味著

e1 e2 | e3

等效於

[e1 e2] | e3

假設您想檢查塊元素是整數還是小數

parse [36] [integer! | decimal!]
; == true
parse [37.2] [integer! | decimal!]
; == true

注意:有序選擇操作的優先順序導致

["a" | "ab"]

中的第二個 subexpression 永遠不會成功,因為第一個具有優先權。

重複運算子

[編輯 | 編輯原始碼]

重複運算子指定給定 subexpression 應該匹配多少次。重複的一般語法是

 repetition_operator subexpression

重複運算子的優先順序高於序列運算子,這意味著

repetition_operator subexpression_1 subexpression_2

[repetition_operator subexpression_1] subexpression_2

相同。所有重複運算子都是貪婪的,這意味著它們始終儘可能多地匹配。

重複運算子是左結合的,這意味著

any 2 skip

等效於

[any 2] skip

tothruinto 運算子也是如此。

零次或一次

[編輯 | 編輯原始碼]

此運算子使用opt關鍵字。它也稱為可選匹配。由於允許零計數,因此此運算子始終成功。

示例

parse "," [opt #","]
; == true
parse "" [opt #","]
; == true

一次或多次

[編輯 | 編輯原始碼]

此運算子使用some關鍵字。

示例

parse "," [some #","]
; == true
parse ",," [some #","]
; == true

零次或多次

[編輯 | 編輯原始碼]

此運算子使用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運算子成功地只匹配了輸入的第一個元素,然後停止,沒有到達尾部

重複計數

[編輯 | 編輯原始碼]

此操作的一般形式是

n subexpression

其中 N 是一個整數值。此操作指定給定 subexpression 的重複計數。

示例

parse "12" [2 skip]
; == true

表示式檢查是否有正好兩個詞,不多不少

parse [Hi Bye] [2 word!]
; == true

次數範圍

[編輯 | 編輯原始碼]

此操作的一般形式是

n m subexpression

其中 N 和 M 是整數值。此操作指定 subexpression 的重複範圍。

示例

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 運算子根據給定的解析 subexpression 推進輸入位置

  • to運算子
  • thru運算子

操作的一般語法是

to subexpression

thru subexpression

to運算子的目的是將輸入位置推進直到成功匹配 subexpression 的位置。

thru運算子的目的是將輸入位置推進到成功匹配 subexpression 之後的位置

如果未找到成功的 subexpression 匹配,則這兩個操作都會失敗。

subexpression 可以是

  • 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

子塊解析

[edit | edit source]

解析塊時,你可能需要檢查它的子塊(或括號、路徑、字面量路徑或獲取路徑)是否存在特定模式。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

使用輸入序列中的資料

[edit | edit source]

你也可以使用序列中的資料來用於你的觸發程式碼。除了使用設定詞來獲取輸入位置之外,還有以下 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

我們可以將序列的正常函式應用於從我們正在解析的序列中提取資料。

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"

R2 和 R3 解析之間的區別

[edit | edit source]

fail - R3 parse 的新關鍵字,一個不匹配任何輸入的非終結符

quote 值 - R3 parse 的新關鍵字,按原樣匹配值

if (表示式) - R3 parse 的新關鍵字,在括號中計算表示式,如果結果是 falsenone,則將其視為匹配失敗

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 子規則 - R3 parse 的新關鍵字,由 Gabriele 提議

複雜的解析表示式

[edit | edit source]

可以構建複雜的解析表示式。當你這樣做的時候,將它們拆分成更小的部分併為它們賦予有意義的名稱會很有用。

遞迴

[edit | edit source]

遞迴通常是描述語法的最優雅的方式。讓我們以一個由 anbn 型別的字串組成的語法為例,其中 n >= 1。可以使用以下解析表示式來描述這種語法。

anbn: ["a" anbn "b" | "ab"]

用法

parse "ab" anbn
; == true
parse "aabb" anbn
; == true

解析習語

[edit | edit source]
描述 操作 習語
Any-string,字串解析 a: ["abc"] a: [#"a" #"b" #"c"][1]
Bitset,字串解析 a: charset ",;" a: [#"," | #";"][2]
skip 非終結符,字串解析 a: [skip] b: complement charset ""[3]
a: [b][4]
skip 非終結符,塊解析 a: [skip] a: [any-type!][5]
opt 運算子(零或一) a: [opt b] a: [b |][6][7]
any 運算子(零個或多個)[8] a: [any b] a: [b a |][9][10]
some 運算子(一個或多個)[8] a: [some b] a: [b [a |]][11][12]
fail(始終失敗的非終結符) a: [fail] a: [some "a" "a"][13]
a: [end skip][14]
次數範圍 運算子 a: [m n b] a: [(l: min m n k: n - m) l b [k [b | c: fail] | :c]][15][16]
then 運算子[17]
(匹配 B,如果成功,則匹配 C;否則匹配 D)
a: [b then c | d] a: [[b (e: c) | (e: d)] e][18]
not 謂詞[19](反轉成功) a: [not b] a: [b then fail |][20]
a: [[b (c: [fail]) | (c: none)] c]
and 謂詞(匹配但不前進) a: [and b] a: [c: b :c][21]
a: [not not b][22]
a: [[b (c: none) | (c: [fail])] fail | c][23][24]
end 非終結符(匹配輸入的尾部) a: [end] a: [not skip][25][26]
start 非終結符(匹配輸入的頭部)[27] a: [start] a: [b: (c: unless head? b [[fail]]) c][28][29]
to 運算子[8]
(前進到第一個成功的匹配)
a: [to b] a: [and b | skip a][30][31][32]
a: [any [not [b (c: none)] (c: [fail]) skip] c][33]
a: [any [[b (c: none d: [fail]) | (c: [fail] d: [skip])] d] c][34]
a: [thru [and b]][35]
toany 之間的對應關係 a: [to [b | end]][36] a: [any [not b skip]]
thru 運算子[8]
(前進到第一個成功的匹配)
a: [thru b] a: [b | skip a][37][38][39]
a: [any [not [b c: (d: [:c])] (d: [fail]) skip] d][33]
a: [any [[b c: (d: [:c] e: [fail]) | (d: [fail] e: [skip])] e] d][34]
a: [to [b c:] :c][35]
set 運算子
(將變數設定為第一個匹配的值)
a: [set b c] f: [(set/any [b] if lesser? index? e index? d [e])][40][41][42]
a: [and [c d:] e: f :d][43]
copy 運算子
(將變數設定為匹配的序列)
a: [copy b c] f: [(b: if lesser? index? e index? d [copy/part e d])][44][42]
a: [and [c d:] e: f :d][43]
quote 運算子,塊解析(匹配終結符) a: [quote b] a: [copy c skip (d: unless equal? c [b] [[fail]]) d][45][46]

該表格說明了

  1. 解析字串時,字串充當字元序列
  2. 解析字串時,位集充當字元選擇
  3. 包含所有字元的位集可以定義為不包含任何字元的位集的補集
  4. 解析字串時,skip 非終結符充當包含所有字元的位集
  5. 解析塊時,skip 非終結符充當ANY-TYPE! 資料型別
  6. opt 運算子可以使用常見的選擇來定義
  7. opt 是貪婪的,因為第一個選擇子表示式優先
  8. a b c d 一個迭代運算子替換一個常見的遞迴非終結符:
    • 增強表達能力(節省非終結符定義)
    • 最佳化記憶體使用(節省堆疊空間)
    • 最佳化速度
  9. any 運算子可以使用常見的遞迴表示式來定義
  10. any 是貪婪的,因為第一個選擇子表示式優先
  11. some 運算子可以使用常見的遞迴表示式來定義
  12. some 是貪婪的,因為第一個選擇子表示式優先
  13. fail 非終結符可以定義(即使沒有end 關鍵字!) 使用some 的貪婪性
  14. fail 版本使用endskip 關鍵字更簡潔,雖然
  15. 時間範圍 運算子可以使用重複序列來定義
  16. 時間範圍 運算子是貪婪的,因為第一個選擇子表示式優先
  17. then 運算子,增強了明顯的表達能力,用在廣義 TDPL
  18. then 運算子可以使用選擇和計算的非終結符來定義
  19. "謂詞" 意味著它不會推進輸入位置
  20. not 謂詞可以使用then 運算子和 fail 非終結符來定義
  21. and 謂詞可以使用位置操作來定義(警告:這不是遞迴安全的;非終結符 B 必須不改變變數 'c 的值!)
  22. and 謂詞可以使用not 謂詞來定義
  23. and 謂詞可以使用選擇、序列和計算的非終結符來定義
  24. 解釋:主選擇的第一個子表示式是一個序列,它被設計為始終失敗,計算一個非推進的非終結符 C,以便 C 成功,如果 B 成功
  25. end 非終結符可以使用not 謂詞來定義(注意這不是一個迴圈定義!)
  26. 這種習慣用法很好地解釋了為什麼 [end skip] 習慣用法總是失敗
  27. 一些使用者建議,檢測輸入序列是否處於頭部可能很有用
  28. start 非終結符可以使用一個序列和一個計算的非終結符來定義
  29. 解釋:C 被計算為失敗,除非輸入位置位於輸入序列的頭部
  30. to 運算子可以使用and 運算子和一個常見的遞迴表示式來定義
  31. 遞迴定義比當前的to 運算子更通用,支援任何非終結符 B
  32. 遞迴定義與to 運算子的行為相同,除了:
    • [to ""] 表示式在解析字串時總是失敗,而遞迴表示式總是成功
  33. a b 這是一個使用anynotfailskip 和計算的非終結符的等效迭代定義
  34. a b 這是一個擴充套件 not 習慣用法的等效迭代定義
  35. a b 這顯示了tothru 運算子之間的關係
  36. | END 選擇導致to 運算子總是成功
  37. thru 運算子可以使用一個常見的遞迴表示式來定義
  38. 遞迴定義比當前的thru 運算子更通用,支援任何非終結符 B
  39. 遞迴定義與thru 運算子的行為相同,除了:
    • [thru end] 表示式總是失敗,而遞迴表示式總是成功
    • [thru ""] 表示式在解析字串時總是失敗,而遞迴表示式總是成功
  40. set 運算子可以使用and 運算子、位置操作、動作和序列來定義
  41. 注意,這個set 定義不限於塊解析
  42. a b 解釋:使用 D 和 E 設定 'b 變數,根據需要。注意,如果輸入位置沒有向前移動,'b 必須設定為 NONE
  43. a b 解釋:序列中的第一個子表示式被定義,以便我們知道 B 匹配後的位置(用於設定 'd)和之前的位置(用於設定 'e)
  44. copy 運算子可以使用 and 運算子、位置操作、動作和序列來定義
  45. quote 成語使用示例:a: [copy c skip (d: unless equal? c ['hi] [[fail]]) d] 匹配文字詞 'hi
  46. 解釋:除非輸入的第一個元素等於給定的終端,否則計算 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 ***
華夏公益教科書