免责声明:我保留它是因为有些东西可能对其他人有用,但是,它并不能解决我最初尝试做的事情。
现在,我正在尝试解决以下问题:
给定类似 {a, B, {c, D}} 的内容,我想扫描提供给 parse_transform/2 的 Erlang 表单,并找到发送运算符 (!) 的每次使用。然后我想检查正在发送的消息并确定它是否适合模式 {a, B, {c, D}}。
因此,考虑找到以下形式:
{op,17,'!',
{var,17,'Pid'},
{tuple,17,[{atom,17,a},{integer,17,5},{var,17,'SomeVar'}]}}]}]}
由于正在发送的消息是:
{tuple,17,[{atom,17,a},{integer,17,5},{var,17,'SomeVar'}]}
这是 {a, 5, SomeVar} 的编码,这将匹配 {a, B, {c, D}} 的原始模式。
我不确定我将如何解决这个问题,但你知道任何可以提供帮助的 API 函数吗?
将给定的 {a, B, {c, D}} 转换为一种形式是可能的,方法是首先用一些东西代替变量,例如字符串(并记下这一点),否则它们将被解除绑定,然后使用:
> erl_syntax:revert(erl_syntax:abstract({a, "B", {c, "D"}})).
{tuple,0,
[{atom,0,a},
{string,0,"B"},
{tuple,0,[{atom,0,c},{string,0,"D"}]}]}
我在想,像这样把它们做成相同的格式后,我可以一起分析它们:
> erl_syntax:type({tuple,0,[{atom,0,a},{string,0,"B"},{tuple,0,[{atom,0,c},string,0,"D"}]}]}).
tuple
%% check whether send argument is also a tuple.
%% then, since it's a tuple, use erl_syntax:tuple_elements/1 and keep comparing in this way, matching anything when you come across a string which was a variable...
我想我最终会遗漏一些东西(例如识别一些东西而不是其他东西......即使它们应该匹配)。我可以使用任何 API 函数来简化此任务吗?至于模式匹配测试运算符或类似的东西,那不存在吗?(即仅在此处建议:http: //erlang.org/pipermail/erlang-questions/2007-December/031449.html)。
编辑:(这次从头开始解释)
如果您使用 t_from_term/1 返回的 erl_type() 即 t_from_term/1 接受一个没有自由变量的术语,那么按照 Daniel 下面的建议使用 erl_types 可能是可行的,因此您必须继续更改类似{a, B, {c, D}}
into的内容{a, '_', {c, '_'}}
(即填充变量),使用 t_from_term/1 然后遍历返回的数据结构,并使用模块的 t_var/1 或其他东西将“_”原子更改为变量。
在解释我最终是如何解决这个问题之前,让我更好地说明这个问题。
问题
我正在开发一个宠物项目(ErlAOP 扩展),准备好后我将托管在 SourceForge 上。基本上,另一个项目已经存在(ErlAOP),通过它可以在函数调用之前/之后/周围/等...注入代码(如果感兴趣,请参阅文档)。
我想扩展它以支持在发送/接收级别注入代码(因为另一个项目)。我已经这样做了,但在托管项目之前,我想进行一些改进。
目前,我的实现只是找到了发送运算符或接收表达式的每次使用,并在之前/之后/周围注入了一个函数(由于尾递归,接收表达式有一些问题)。我们称这个函数为 dmfun(动态匹配函数)。
用户将指定当发送 {a, B, {c, D}} 形式的消息时,应在发送之前评估函数 do_something/1。因此,当前的实现在源代码中每次使用 send 操作之前都会注入 dmfun。然后 Dmfun 会有类似的东西:
case Arg of
{a, B, {c, D}} -> do_something(Arg);
_ -> continue
end
其中 Arg 可以简单地传递给 dmfun/1 因为您可以访问从源代码生成的表单。
所以问题是任何发送操作员都会在它之前注入 dmfun/1 (并且发送操作的消息作为参数传递)。但是当发送像 50, {a, b}, [6, 4, 3] 等这样的消息时......这些消息肯定不会匹配 {a, B, {c, D}},所以在发送时注入 dmfun/1这些消息是一种浪费。
我希望能够挑选出合理的发送操作,例如 Pid !{a, 5, SomeVar} 或 Pid !{a, X, SomeVar}。在这两种情况下,注入 dmfun/1 是有意义的,因为如果在运行时 SomeVar = {c, 50},则应评估用户提供的 do_something/1(但如果 SomeVar = 50,则不应该,因为我们对 {a, B, {c, D}} 和 50 不匹配 {c, D} 感兴趣)。
我过早地写了以下内容。它不能解决我遇到的问题。我最终没有包括这个功能。无论如何我都留下了解释,但如果由我决定,我会完全删除这篇文章......我仍在尝试,我认为这里的内容对任何人都没有任何用处。
在解释之前,让:
msg_format = 用户提供的消息格式,它将确定正在发送/接收的哪些消息是有趣的(例如 {a, B, {c, D}})。
msg = 在源代码中发送的实际消息(例如 Pid ! {a, X, Y})。
我在之前的编辑中给出了下面的解释,但后来发现它与它应该匹配的一些东西不匹配。例如,当 msg_format = {a, B, {c, D}}, msg = {a, 5, SomeVar} 不匹配时(通过“匹配”我的意思是应该注入 dmfun/1。
让我们将下面概述的“算法”称为 Alg。我采取的方法是执行 Alg(msg_format, msg) 和 Alg(msg, msg_format)。下面的解释只通过其中之一。通过重复相同的事情只获得不同的匹配函数(matching_fun(msg_format)
而不是matching_fun(msg)
),并且仅当 Alg(msg_format, msg) 或 Alg(msg, msg_format) 中的至少一个返回 true 时才注入 dmfun/1,那么结果应该是注入在 dmfun/1 中,可以在运行时实际生成所需的消息。
采用您在提供给 parse_transform/2 的 [Forms] 中找到的消息表单,例如,假设您找到:
{op,24,'!',{var,24,'Pid'},{tuple,24,[{atom,24,a},{var,24,'B'},{var,24,'C'}]}}
所以您将采用{tuple,24,[{atom,24,a},{var,24,'B'},{var,24,'C'}]}
正在发送的消息。(绑定到消息)。执行 fill_vars(Msg) 其中:
-define(VARIABLE_FILLER, "_"). -spec fill_vars(erl_parse:abstract_form()) -> erl_parse:abstract_form(). %% @doc This function takes an abstract_form() and replaces all {var, LineNum, Variable} forms with %% {string, LineNum, ?VARIABLE_FILLER}. fill_vars(Form) -> erl_syntax:revert( erl_syntax_lib:map( fun(DeltaTree) -> case erl_syntax:type(DeltaTree) of variable -> erl_syntax:string(?VARIABLE_FILLER); _ -> DeltaTree end end, Form)).
在 2 的输出上执行 form_to_term/1,其中:
form_to_term(Form) -> element(2, erl_eval:exprs([Form], [])).
在 3 的输出上执行 term_to_str/1,其中:
-define(inject_str(FormatStr, TermList), lists:flatten(io_lib:format(FormatStr, TermList))). term_to_str(Term) -> ?inject_str("~p", [Term]).
Do
gsub(v(4), "\"_\"", "_")
,其中 v(4) 是 4 的输出,gsub 是:(取自此处)gsub(Str,Old,New) -> RegExp = "\\Q"++Old++"\\E", re:replace(Str,RegExp,New,[global, multiline, {return, list}]).
将变量(例如 M)绑定到 matching_fun(v(5)),其中:
matching_fun(StrPattern) -> form_to_term( str_to_form( ?inject_str( "fun(MsgFormat) -> case MsgFormat of ~s -> true; _ -> false end end.", [StrPattern]) ) ). str_to_form(MsgFStr) -> {_, Tokens, _} = erl_scan:string(end_with_period(MsgFStr)), {_, Exprs} = erl_parse:parse_exprs(Tokens), hd(Exprs). end_with_period(String) -> case lists:last(String) of $. -> String; _ -> String ++ "." end.
最后,采用用户提供的消息格式(以字符串形式给出),例如 MsgFormat = "{a, B, {c, D}}",然后执行:MsgFormatTerm = form_to_term(fill_vars(str_to_form(MsgFormat)))。然后你可以 M(MsgFormatTerm)。
例如,用户提供的消息格式 = {a, B, {c, D}} 和 Pid !{a, B, C} 在代码中找到:
2> weaver_ext:fill_vars({tuple,24,[{atom,24,a},{var,24,'B'},{var,24,'C'}]}).
{tuple,24,[{atom,24,a},{string,0,"_"},{string,0,"_"}]}
3> weaver_ext:form_to_term(v(2)).
{a,"_","_"}
4> weaver_ext:term_to_str(v(3)).
"{a,\"_\",\"_\"}"
5> weaver_ext:gsub(v(4), "\"_\"", "_").
"{a,_,_}"
6> M = weaver_ext:matching_fun(v(5)).
#Fun<erl_eval.6.13229925>
7> MsgFormatTerm = weaver_ext:form_to_term(weaver_ext:fill_vars(weaver_ext:str_to_form("{a, B, {c, D}}"))).
{a,"_",{c,"_"}}
8> M(MsgFormatTerm).
true
9> M({a, 10, 20}).
true
10> M({b, "_", 20}).
false