4

免责声明:我保留它是因为有些东西可能对其他人有用,但是,它并不能解决我最初尝试做的事情。

现在,我正在尝试解决以下问题:

给定类似 {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 中,可以在运行时实际生成所需的消息。

  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'}]}正在发送的消息。(绑定到消息)。

  2. 执行 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)).
    
  3. 在 2 的输出上执行 form_to_term/1,其中:

    form_to_term(Form) -> element(2, erl_eval:exprs([Form], [])).
    
  4. 在 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]).
    
  5. 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}]).
    
  6. 将变量(例如 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.
    
  7. 最后,采用用户提供的消息格式(以字符串形式给出),例如 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
4

2 回答 2

2

erl_types(HiPE)中有此功能。

不过,我不确定您是否拥有使用此模块的正确格式的数据。我似乎记得它需要 Erlang 术语作为输入。如果您解决了表单问题,您应该能够使用erl_types:t_from_term/1和做大部分您需要的事情erl_types:t_is_subtype/2

我上次使用这些是很久以前的事了,我只做过测试运行时,而不是编译时。如果您想从我的旧代码(不再工作)中查看使用模式,您可以在 github上找到它。

于 2012-06-26T20:38:06.437 回答
0

在一般情况下,我认为这在编译时是不可能的。考虑:

send_msg(Pid, Msg) ->
    Pid ! Msg.

Msg看起来像 aa var,这是一个完全不透明的类型。您无法判断它是元组、列表还是原子,因为任何人都可以使用为Msg.

相反,这在运行时会容易得多。每次使用!运算符时,您都需要调用一个包装函数,它会尝试匹配您尝试发送的消息,并在模式匹配时执行额外的处理。

于 2012-06-27T00:06:47.987 回答