檢視原始碼 Erlang 中的匹配規範

「匹配規範」(match_spec)是一個 Erlang 詞彙,用於描述嘗試匹配某項事物的簡短「程式」。它可以用於透過 erlang:trace_pattern/3 控制追蹤,或使用例如 ets:select/2 在 ETS 表格中搜尋物件。匹配規範在許多方面都像 Erlang 中的小型函式,但它是由 Erlang 執行時系統解譯/編譯,效率遠高於呼叫 Erlang 函式。與真正的 Erlang 函式的表達能力相比,匹配規範也受到很大的限制。

匹配規範與 Erlang fun 之間最顯著的差異在於語法。匹配規範是 Erlang 詞彙,而非 Erlang 程式碼。此外,匹配規範對於例外狀況有一個奇怪的概念。

  • MatchCondition 部分(類似 Erlang guard)中的例外狀況(例如 badarg)會產生立即失敗。
  • MatchBody 部分(類似 Erlang 函式主體)中的例外狀況會被隱式捕獲,並產生單一原子 'EXIT'

文法

用於追蹤的匹配規範可以用以下非正式文法描述

  • MatchExpression ::= [ MatchFunction, ... ]
  • MatchFunction ::= { MatchHead, MatchConditions, MatchBody }
  • MatchHead ::= MatchVariable | '_' | [ MatchHeadPart, ... ]

  • MatchHeadPart ::= term() | MatchVariable | '_'

  • MatchVariable ::= '$<number>'
  • MatchConditions ::= [ MatchCondition, ...] | []

  • MatchCondition ::= { GuardFunction } | { GuardFunction, ConditionExpression, ... }

  • BoolFunction ::= is_atom | is_float | is_integer | is_list | is_number | is_pid | is_port | is_reference | is_tuple | is_map | is_map_key | is_binary | is_bitstring | is_boolean | is_function | is_record | is_seq_trace | 'and' | 'or' | 'not' | 'xor' | 'andalso' | 'orelse'

  • ConditionExpression ::= ExprMatchVariable | { GuardFunction } | { GuardFunction, ConditionExpression, ... } | TermConstruct

  • ExprMatchVariable ::= MatchVariable (在 MatchHead 中繫結) | '$_' | '$$'

  • TermConstruct = {{}} | {{ ConditionExpression, ... }} | [] | [ConditionExpression, ...] | #{} | #{term() => ConditionExpression, ...} | NonCompositeTerm | Constant

  • NonCompositeTerm ::= term() (非列表或元組或映射)
  • Constant ::= {const, term()}
  • GuardFunction ::= BoolFunction | abs | element | hd | length | map_get | map_size | max | min | node | float | round | floor | ceil | size | bit_size | byte_size | tuple_size | tl | trunc | binary_part | '+' | '-' | '*' | 'div' | 'rem' | 'band' | 'bor' | 'bxor' | 'bnot' | 'bsl' | 'bsr' | '>' | '>=' | '<' | '=<' | '=:=' | '==' | '=/=' | '/=' | self | get_tcw

  • MatchBody ::= [ ActionTerm ]
  • ActionTerm ::= ConditionExpression | ActionCall

  • ActionCall ::= {ActionFunction} | {ActionFunction, ActionTerm, ...}

  • ActionFunction ::= set_seq_token | get_seq_token | message | return_trace | exception_trace | process_dump | enable_trace | disable_trace | trace | display | caller | caller_line | current_stacktrace | set_tcw | silent

用於 ets 的匹配規範可以用以下非正式文法描述

  • MatchExpression ::= [ MatchFunction, ... ]
  • MatchFunction ::= { MatchHead, MatchConditions, MatchBody }
  • MatchHead ::= MatchVariable | '_' | { MatchHeadPart, ... }

  • MatchHeadPart ::= term() | MatchVariable | '_'

  • MatchVariable ::= '$<number>'
  • MatchConditions ::= [ MatchCondition, ...] | []

  • MatchCondition ::= { GuardFunction } | { GuardFunction, ConditionExpression, ... }

  • BoolFunction ::= is_atom | is_float | is_integer | is_list | is_number | is_pid | is_port | is_reference | is_tuple | is_map | is_map_key | is_binary | is_bitstring | is_boolean | is_function | is_record | 'and' | 'or' | 'not' | 'xor' | 'andalso' | 'orelse'

  • ConditionExpression ::= ExprMatchVariable | { GuardFunction } | { GuardFunction, ConditionExpression, ... } | TermConstruct

  • ExprMatchVariable ::= MatchVariable (在 MatchHead 中繫結) | '$_' | '$$'

  • TermConstruct = {{}} | {{ ConditionExpression, ... }} | [] | [ConditionExpression, ...] | #{} | #{term() => ConditionExpression, ...} | NonCompositeTerm | Constant

  • NonCompositeTerm ::= term() (非列表或元組或映射)
  • Constant ::= {const, term()}
  • GuardFunction ::= BoolFunction | abs | element | hd | length | map_get | map_size | max | min | node | float | round | floor | ceil | size | bit_size | byte_size | tuple_size | tl | trunc | binary_part | '+' | '-' | '*' | 'div' | 'rem' | 'band' | 'bor' | 'bxor' | 'bnot' | 'bsl' | 'bsr' | '>' | '>=' | '<' | '=<' | '=:=' | '==' | '=/=' | '/=' | self

  • MatchBody ::= [ ConditionExpression, ... ]

函式描述

所有類型的匹配規範中允許的函式

match_spec 中允許的函式運作方式如下

  • is_atomis_booleanis_floatis_integeris_listis_numberis_pidis_portis_referenceis_tupleis_mapis_binaryis_bitstringis_function - 與 Erlang 中對應的 guard 測試相同,傳回 truefalse

  • is_record - 接受一個額外的參數,此參數必須record_info(size, <record_type>) 的結果,例如 {is_record, '$1', rectype, record_info(size, rectype)}

  • 'not' - 對其單一引數取反(任何不是 false 的值都會傳回 false)。

  • 'and' - 如果其所有引數(可變長度的引數列表)評估為 true,則傳回 true,否則傳回 false。評估順序未定義。

  • 'or' - 如果其任何引數評估為 true,則傳回 true。可變長度的引數列表。評估順序未定義。

  • 'andalso' - 與 'and' 的運作方式相同,但當一個引數評估為非 true 的值時,會停止評估其引數。引數會從左到右評估。

  • 'orelse' - 與 'or' 的運作方式相同,但只要其一個引數評估為 true,就會停止評估。引數會從左到右評估。

  • 'xor' - 只有兩個引數,其中一個必須為 true,另一個必須為 false,才會傳回 true;否則,'xor' 會傳回 false。

  • abselementhdlengthmap_getmap_sizemaxminnoderoundceilfloorfloatsizebit_sizebyte_sizetuple_sizetltruncbinary_part'+''-''*''div''rem''band''bor''bxor''bnot''bsl''bsr''>''>=''<''=<''=:=''==''=/=''/='self - 與對應的 Erlang BIF(或運算子)相同。如果引數錯誤,結果取決於上下文。在表示式的 MatchConditions 部分中,測試會立即失敗(如 Erlang guard)。在 MatchBody 部分中,例外狀況會被隱式捕獲,而呼叫會產生原子 'EXIT'

僅允許用於追蹤的函式

僅允許用於追蹤的函式運作方式如下

  • is_seq_trace - 如果為目前的程序設定了循序追蹤權杖,則傳回 true,否則傳回 false

  • set_seq_token - 與 seq_trace:set_token/2 的運作方式相同,但成功時傳回 true,錯誤或引數錯誤時傳回 'EXIT'。僅允許在 MatchBody 部分中使用,且僅允許在追蹤時使用。

  • get_seq_token - 與 seq_trace:get_token/0 相同,且僅允許在追蹤時的 MatchBody 部分使用。

  • message - 設定附加於傳送的追蹤訊息的額外訊息。在主體中只能設定一個額外訊息。後續的呼叫會取代附加的訊息。

    在特殊情況下,{message, false} 會停用此函式呼叫的追蹤訊息傳送('call' 和 'return_to'),如同比對規範未匹配一樣。如果只需要 MatchBody 部分的副作用,這會很有用。

    另一種特殊情況是 {message, true},它會設定預設行為,如同該函式沒有比對規範一樣;追蹤訊息會在沒有額外資訊的情況下傳送(如果 {message, true} 之前沒有其他對 message 的呼叫,它實際上是一個「空操作」)。

    接受一個參數:訊息。傳回 true,且僅能於追蹤時在 MatchBody 部分使用。

  • return_trace - 導致在目前函式返回時傳送 return_from 追蹤訊息。不接受任何引數,傳回 true,且僅能於追蹤時在 MatchBody 部分使用。如果程序追蹤旗標 silent 處於啟用狀態,則會抑制 return_from 追蹤訊息。

    警告: 如果追蹤的函式是尾遞迴,此比對規範函式會破壞該屬性。因此,如果對永久伺服器程序使用執行此函式的比對規範,則只能在有限的時間內啟用,否則模擬器最終會使用主機中的所有記憶體並當機。如果使用程序追蹤旗標 silent 抑制此比對規範函式,則尾遞迴仍然保留。

  • exception_trace - 功能與 return_trace 相同,另外,如果追蹤的函式因為例外狀況而退出,則無論例外狀況是否被捕獲,都會產生 exception_from 追蹤訊息。

  • process_dump - 以二進制形式傳回有關目前程序的一些文字資訊。不接受任何引數,且僅允許在追蹤時於 MatchBody 部分使用。

  • enable_trace - 為程序啟用追蹤旗標。

    帶有一個參數時,此函式會啟用追蹤,如同 Erlang 呼叫 trace:process(S, self(), true, [P2]),其中 S 是目前的追蹤會期,而 P2enable_trace 的參數。

    帶有兩個參數時,第一個參數必須是程序識別碼或程序的註冊名稱。在這種情況下,會為指定的程序啟用追蹤,如同 Erlang 呼叫 trace:process(S, P1, true, [P2]),其中 P1 是第一個參數,而 P2 是第二個參數。P1不能是原子 allnewexisting 中的其中一個(除非它們是註冊名稱)。P2不能cpu_timestamptracer

    傳回 true,且僅能於追蹤時在 MatchBody 部分使用。

    如果由舊版函式 erlang:trace_pattern/3 使用,則程序 P1 會將其追蹤訊息傳送到執行此陳述式之程序所使用的相同追蹤器。

  • disable_trace - 為程序停用追蹤旗標。

    帶有一個參數時,此函式會停用追蹤,如同 Erlang 呼叫 trace:process(S, self(), false, [P2]),其中 S 是目前的追蹤會期,而 P2disable_trace 的參數。

    帶有兩個參數時,此函式的作用如同 Erlang 呼叫 trace:process(S, P1, false, [P2]),其中 P1 可以是程序識別碼或註冊名稱,並指定為比對規範函式的第一個參數。P2不能cpu_timestamptracer

    傳回 true,且僅能於追蹤時在 MatchBody 部分使用。

  • trace - 為程序啟用和/或停用追蹤旗標。

    帶有兩個參數時,此函式會將要停用的追蹤旗標清單作為第一個參數,並將要啟用的追蹤旗標清單作為第二個參數。邏輯上,會先套用停用清單,但實際上所有變更都會以原子方式套用。追蹤旗標與 trace:process/4 的相同,不包括 cpu_timestamp

    當此函式帶有三個參數時,第一個參數是要設定追蹤旗標的程序識別碼或程序的註冊名稱,第二個參數是停用清單,而第三個參數是啟用清單。

    當透過新的 trace API 使用時,不允許使用追蹤旗標 tracer,且接收追蹤器永遠是目前會期的追蹤器。

    當透過舊版函式 erlang:trace_pattern/3 使用時,允許使用追蹤旗標 tracer。如果未指定追蹤器,則會使用與執行比對規範的程序相同的追蹤器(而非中繼追蹤器)。如果該程序也沒有追蹤器,則會忽略追蹤旗標。

    當使用 追蹤器模組時,必須在執行比對規範之前載入該模組。如果未載入,則比對會失敗。

    如果變更了追蹤目標程序的任何追蹤屬性,則傳回 true,否則傳回 false。僅能於追蹤時在 MatchBody 部分使用。

  • caller - 以元組 {Module, Function, Arity} 的形式傳回呼叫函式,如果無法判斷呼叫函式,則傳回原子 undefined。僅能於追蹤時在 MatchBody 部分使用。

    請注意,如果追蹤的是「技術內建函式」(即不是以 Erlang 編寫的函式),則 caller 函式有時會傳回原子 undefined。在這種呼叫期間,無法使用呼叫的 Erlang 函式。

  • caller_line - 與 caller 類似,但會傳回有關呼叫函式中函式呼叫位置的原始程式碼的其他資訊。以元組 {Module, Function, Arity, {File, Line}} 的形式傳回呼叫函式。File字串檔案名稱,而 Line 是原始程式碼行號。如果無法判斷 FileLine,則會傳回 {Module, Function, Arity, undefined}。如果無法判斷呼叫函式,則會傳回原子 undefined。僅能於追蹤時在 MatchBody 部分使用。

    請注意,如果追蹤的是「技術內建函式」(即不是以 Erlang 編寫的函式),則 caller_line 函式有時會傳回原子 undefined。在這種呼叫期間,無法使用呼叫的 Erlang 函式。

  • current_stacktrace - 傳回呼叫函式的目前呼叫堆疊回溯(堆疊追蹤)。堆疊的格式與 trycatch 部分中的格式相同。請參閱 呼叫堆疊回溯 (stacktrace)。堆疊追蹤的深度會根據 backtrace_depth 系統旗標設定而截斷。

    接受深度參數。如果引數較大,深度值將為 backtrace_depth

  • display - 僅用於偵錯目的。在 stdout 上將單一引數顯示為 Erlang 項,這很少是想要的結果。傳回 true,且僅能於追蹤時在 MatchBody 部分使用。

  • get_tcw - 不接受任何引數,並傳回節點的追蹤控制字的值。erlang:system_info(trace_control_word) 會執行相同的操作。

    追蹤控制字是一個 32 位元無符號整數,旨在用於通用追蹤控制。可以從追蹤比對規範內部和使用 BIF 來測試和設定追蹤控制字。此呼叫僅允許在追蹤時進行。

  • set_tcw - 接受一個無符號整數引數,將節點的追蹤控制字的值設定為引數的值,並傳回先前的值。erlang:system_flag(trace_control_word, Value) 會執行相同的操作。僅允許在追蹤時於 MatchBody 部分使用 set_tcw

  • silent - 接受一個引數。如果引數為 true,則目前程序的呼叫追蹤訊息模式會設定為靜音,以用於此呼叫和所有後續呼叫,也就是說,即使在追蹤函式的 MatchBody 部分呼叫 {message, true},也會抑制呼叫追蹤訊息。

    此模式也可以透過 erlang:trace/3 的旗標 silent 啟用。

    如果引數為 false,則目前程序的呼叫追蹤訊息模式會設定為正常(非靜音),以用於此呼叫和所有後續呼叫。

    如果引數不是 truefalse,則呼叫追蹤訊息模式不受影響。

注意

所有「函式呼叫」都必須是元組,即使它們不接受任何引數。self 的值是原子() self,但 {self} 的值是目前程序的 pid()。

比對目標

每個比對規範的執行都會針對比對目標項進行。目標項的格式和內容取決於執行比對的內容。ETS 的比對目標永遠是完整的表格元組。呼叫追蹤的比對目標永遠是所有函式引數的清單。事件追蹤的比對目標取決於事件類型,請參閱下表。

內容類型比對目標說明
ETS{Key, Value1, Value2, ...}表格物件
追蹤呼叫[Arg1, Arg2, ...]函式引數
追蹤傳送[Receiver, Message]接收程序/連接埠和訊息項
追蹤'receive'[Node, Sender, Message]傳送節點、處理序/埠和訊息項

表格:根據上下文比對目標

變數和字面值

變數的形式為 '$<數字>',其中 <數字> 是介於 0 和 100,000,000 (1e+8) 之間的整數。如果數字超出這些限制,則行為是未定義的。在 MatchHead 部分,特殊變數 '_' 會比對任何值,並且永遠不會被綁定(就像 Erlang 中的 _ 一樣)。

  • MatchCondition/MatchBody 部分中,不允許未綁定的變數,因此 '_' 會被解釋為它自己(一個原子)。變數只能在 MatchHead 部分中被綁定。
  • MatchBodyMatchCondition 部分中,只能使用先前綁定的變數。
  • 作為一種特殊情況,以下適用於 MatchCondition/MatchBody 部分
    • 變數 '$_' 會展開為整個比對目標項。
    • 變數 '$$' 會展開為依序排列的所有已綁定變數的值列表(也就是 ['$1','$2', ...])。

MatchHead 部分,所有字面值(上述變數除外)都會被「照原樣」解釋。

MatchCondition/MatchBody 部分,解釋方式在某些方面有所不同。這些部分中的字面值可以「照原樣」寫入,這適用於所有字面值,除了元組之外,或者使用特殊形式 {const, T},其中 T 是任何 Erlang 項。

對於比對規格中的元組字面值,也可以使用雙元組括號,也就是將它們構造成包含單一元組的一元組,而該單一元組是要被構建的元組。「雙元組括號」語法可用於從已綁定的變數建構元組,例如 {{'$1', [a,b,'$2']}}。範例

表達式變數綁定結果
{{'$1','$2'}}'$1' = a, '$2' = b{a,b}
{const, {'$1', '$2'}}不相關{'$1', '$2'}
a不相關a
'$1''$1' = [][]
[{{a}}]不相關[{a}]
['$1']'$1' = [][[]]
42不相關42
"hello"不相關"hello"
$1不相關49(字元 '1' 的 ASCII 值)

表格:比對規格的 MatchCondition/MatchBody 部分中的字面值

比對的執行

當執行階段系統決定是否要傳送追蹤訊息時,比對表達式的執行方式如下

對於 MatchExpression 列表中的每個元組,並且在沒有比對成功的情況下

  1. MatchHead 部分與比對目標項進行比對,綁定 '$<數字>' 變數(很像 ets:match/2 中的方式)。如果 MatchHead 部分無法比對引數,則比對失敗。
  2. 評估每個 MatchCondition (其中只能出現先前在 MatchHead 部分中綁定的 '$<數字>' 變數),並期望它傳回原子 true。當條件的評估結果不是 true 時,比對失敗。如果任何 BIF 呼叫產生例外,比對也會失敗。
  3. 可能會發生兩種情況
  • 如果比對規格在追蹤時執行

    以與 MatchConditions 相同的方式評估每個 ActionTerm,但忽略傳回值。無論這部分發生什麼事,比對都會成功。

  • 如果比對規格在從 ETS 表格中選取物件時執行

    依序評估表達式,並傳回最後一個表達式的值(通常在此上下文中只有一個表達式)。

ETS 和追蹤中比對規格的差異

ETS 比對規格會產生傳回值。通常,MatchBody 包含一個單一的 ConditionExpression,其定義傳回值,且不產生任何副作用。在 ETS 上下文中不允許有副作用的呼叫。

追蹤時沒有要產生的傳回值,比對規格會比對或不比對。當表達式比對成功時,其效果是追蹤訊息,而不是傳回的項。ActionTerm 的執行方式與命令式語言相同,也就是為了其副作用而執行。追蹤時也允許有副作用的函數。

追蹤範例

比對三個引數的引數列表,其中第一個和第三個引數相等

[{['$1', '_', '$1'],
  [],
  []}]

比對三個引數的引數列表,其中第二個引數是大於 3 的數字

[{['_', '$1', '_'],
  [{ '>', '$1', 3}],
  []}]

比對三個引數的引數列表,其中第三個引數是包含引數一和二的元組,或者是以引數一和二開頭的列表(也就是 [a,b,[a,b,c]][a,b,{a,b}]

[{['$1', '$2', '$3'],
  [{'orelse',
      {'=:=', '$3', {{'$1','$2'}}},
      {'and',
        {'=:=', '$1', {hd, '$3'}},
        {'=:=', '$2', {hd, {tl, '$3'}}}}}],
  []}]

上述問題也可以如下解決

[{['$1', '$2', {'$1', '$2}], [], []},
 {['$1', '$2', ['$1', '$2' | '_']], [], []}]

比對兩個引數,其中第一個引數是以一個列表開頭的元組,而該列表又以第二個引數的兩倍值開頭(也就是 [{[4,x],y},2][{[8], y, z},4])

[{['$1', '$2'],[{'=:=', {'*', 2, '$2'}, {hd, {element, 1, '$1'}}}],
  []}]

比對三個引數。當所有三個引數都相等且為數字時,將處理序傾印附加到追蹤訊息,否則讓追蹤訊息「照原樣」,但將循序追蹤權杖標籤設定為 4711

[{['$1', '$1', '$1'],
  [{is_number, '$1'}],
  [{message, {process_dump}}]},
 {'_', [], [{set_seq_token, label, 4711}]}]

如上所述,可以將引數列表與單一 MatchVariable'_' 比對。將整個引數列表替換為單一變數是一種特殊情況。在所有其他情況下,MatchHead 必須是適當的列表。

僅當追蹤控制字設定為 1 時,才產生追蹤訊息

[{'_',
  [{'==',{get_tcw},{const, 1}}],
  []}]

僅當有 seq_trace 權杖時,才產生追蹤訊息

[{'_',
  [{'==',{is_seq_trace},{const, 1}}],
  []}]

當第一個引數是 'verbose' 時,移除 'silent' 追蹤旗標;當它是 'silent' 時,則加上它:

[{'$1',
  [{'==',{hd, '$1'},verbose}],
  [{trace, [silent],[]}]},
 {'$1',
  [{'==',{hd, '$1'},silent}],
  [{trace, [],[silent]}]}]

如果函數的arity 為 3,則加入 return_trace 訊息

[{'$1',
  [{'==',{length, '$1'},3}],
  [{return_trace}]},
 {'_',[],[]}]

僅當函數的 arity 為 3 且第一個引數為 'trace' 時,才產生追蹤訊息

[{['trace','$2','$3'],
  [],
  []},
 {'_',[],[]}]

ETS 範例

比對 ETS 表格中的所有物件,其中第一個元素是原子 'strider' 且元組的 arity 為 3,並傳回整個物件

[{{strider,'_','_'},
  [],
  ['$_']}]

比對 ETS 表格中 arity > 1 且第一個元素為 'gandalf' 的所有物件,並傳回元素 2

[{'$1',
  [{'==', gandalf, {element, 1, '$1'}},{'>=',{size, '$1'},2}],
  [{element,2,'$1'}]}]

在此範例中,如果第一個元素是索引鍵,則在 MatchHead 部分中比對該索引鍵,比在 MatchConditions 部分中比對效率更高。表格的搜尋空間會受到 MatchHead 的限制,因此只會搜尋具有相符索引鍵的物件。

比對三個元素的元組,其中第二個元素是 'merry''pippin',並傳回整個物件

[{{'_',merry,'_'},
  [],
  ['$_']},
 {{'_',pippin,'_'},
  [],
  ['$_']}]

函數 ets:test_ms/2 對於測試複雜的 ETS 比對非常有用。