查看源代码 Erlang 中的匹配规范

“匹配规范”(match_spec)是一个 Erlang 术语,用于描述一个尝试匹配某些内容的“小程序”。它可以使用 erlang:trace_pattern/3 来控制跟踪,或者使用例如 ets:select/2 在 ETS 表中搜索对象。在许多方面,匹配规范的工作方式类似于 Erlang 中的一个小函数,但它是由 Erlang 运行时系统解释/编译的,效率远高于调用 Erlang 函数。与真正的 Erlang 函数的表达能力相比,匹配规范也受到很大限制。

匹配规范和 Erlang 函数之间最显着的区别在于语法。匹配规范是 Erlang 术语,而不是 Erlang 代码。此外,匹配规范有一个奇怪的异常概念。

  • MatchCondition 部分(类似于 Erlang 保护)中的异常(例如 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 中相应的保护测试相同,返回 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 保护中一样)。在 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 - 返回调用函数的当前调用堆栈回溯 (stacktrace)。该堆栈的格式与 trycatch 部分中的格式相同。请参阅 调用堆栈回溯(堆栈跟踪)。根据 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 的值是 atom() self,但 {self} 的值是当前进程的 pid()。

匹配目标

每个匹配规范的执行都是针对一个匹配目标项进行的。目标项的格式和内容取决于执行匹配的上下文。ETS 的匹配目标始终是一个完整的表元组。调用跟踪的匹配目标始终是所有函数参数的列表。事件跟踪的匹配目标取决于事件类型,请参见下表。

上下文类型匹配目标描述
ETS{Key, Value1, Value2, ...}一个表对象
跟踪调用[Arg1, Arg2, ...]函数参数
跟踪发送[接收者, 消息]接收进程/端口和消息项
跟踪'接收'[节点, 发送者, 消息]发送节点,进程/端口和消息项

表:取决于上下文的匹配目标

变量和字面量

变量的形式为 '$<number>',其中 <number> 是一个介于 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 部分与匹配目标项进行匹配,绑定 '$<number>' 变量(很像 ets:match/2 中的那样)。如果 MatchHead 部分无法匹配参数,则匹配失败。
  2. 评估每个 MatchCondition(其中只能出现先前在 MatchHead 部分中绑定的 '$<number>' 变量),并期望它返回原子 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' 的所有对象,并返回第二个元素

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

在此示例中,如果第一个元素是键,则在 MatchHead 部分中匹配该键比在 MatchConditions 部分中匹配该键效率更高。表的搜索空间在 MatchHead 方面受到限制,因此仅搜索具有匹配键的对象。

匹配三个元素的元组,其中第二个元素是 'merry''pippin',并返回整个对象

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

函数 ets:test_ms/2 对于测试复杂的 ETS 匹配非常有用。