抽象形式上的模式匹配

时间:2012-06-21 09:06:46

标签: erlang pattern-matching

免责声明:我之所以这样做,是因为有些事情可能对其他人有用,但是,它并没有解决我最初尝试过的事情。

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

给出类似{a,B,{c,D}}之类的内容,我想扫描给予parse_transform / 2的Erlang表单,并找到send运算符(!)的每个用法。然后我想检查发送的消息,并确定它是否适合模式{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(),那么使用以下Daniel建议的erl_types可能是可行的,即t_from_term / 1需要一个没有自由变量的术语,所以你必须保持改变{{ {1}}进入{a, B, {c, D}}(即填充变量),使用t_from_term / 1然后浏览返回的数据结构并更改' _'原子到变量使用模块的t_var / 1或其他东西。

在解释我最终如何解决之前,让我先说明问题。

问题

我正在开展一个宠物项目(ErlAOP扩展),我准备好在SourceForge上托管它。基本上,另一个项目已经存在(ErlAOP),通过该项目可以在/ etc / around函数调用之前/之后注入代码(如果感兴趣,请参阅doc)。

我想扩展它以支持在发送/接收级别注入代码(因为另一个项目)。我已经完成了这个,但在主持项目之前,我想做一些改进。

目前,我的实现只是发现每次使用send运算符或接收表达式并在/ after / around之前注入一个函数(由于尾递归,接收表达式有一些问题)。让我们调用此函数 dmfun (动态匹配函数)。

用户将指定表格的消息,例如正在发送{a,B,{c,D}},然后在发送之前评估函数do_something / 1。因此,当前实现在源代码中每次使用send op之前注入dmfun。然后Dmfun会有类似的东西:

{a, '_', {c, '_'}}

其中Arg可以简单地传递给dmfun / 1,因为您可以访问源代码生成的表单。

所以问题是任何发送操作符都会在它之前注入dmfun / 1(并且send op' s消息作为参数传递)。但是当发送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)。以下解释仅通过其中一个。通过重复相同的事情只获得不同的匹配函数(case Arg of {a, B, {c, D}} -> do_something(Arg); _ -> continue end 而不是matching_fun(msg_format)),并且仅在Alg(msg_format,msg)或Alg(msg,msg_format)中的至少一个时注入dmfun / 1返回true,那么结果应该是注入dmfun / 1,其中可以在运行时实际生成所需的消息。

  1. 获取您在给予parse_transform / 2的[Forms]中找到的消息表格,例如让我们说你找到:matching_fun(msg) 因此,您可以使用{op,24,'!',{var,24,'Pid'},{tuple,24,[{atom,24,a},{var,24,'B'},{var,24,'C'}]}}这是要发送的消息。 (绑定到Msg)。

  2. 请fill_vars(Msg)在哪里:

    {tuple,24,[{atom,24,a},{var,24,'B'},{var,24,'C'}]}
  3. 在2&输出上执行form_to_term / 1,其中:

    -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)).
    
  4. 在3&#39输出上执行term_to_str / 1,其中:

    form_to_term(Form) -> element(2, erl_eval:exprs([Form], [])).
    
  5. 执行-define(inject_str(FormatStr, TermList), lists:flatten(io_lib:format(FormatStr, TermList))). term_to_str(Term) -> ?inject_str("~p", [Term]). ,其中v(4)是4的输出,gsub是:(取自here

    gsub(v(4), "\"_\"", "_")
  6. 将变量(例如M)绑定到matching_fun(v(5)),其中:

    gsub(Str,Old,New) -> RegExp = "\\Q"++Old++"\\E", re:replace(Str,RegExp,New,[global, multiline, {return, list}]).
    
  7. 最后,采用用户提供的消息格式(以字符串形式给出),例如MsgFormat =" {a,B,{c,D}}"和do:MsgFormatTerm = form_to_term(fill_vars(str_to_form(MsgFormat)))。然后你可以M(MsgFormatTerm)。

  8. e.g。用户提供的消息格式= {a,B,{c,D}}和Pid!在代码中找到{a,B,C}:

    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.
    

2 个答案:

答案 0 :(得分:2)

erl_types(HiPE)中有此功能。

我不确定您是否拥有使用此模块的正确格式的数据。我似乎记得它需要Erlang术语作为输入。如果您发现表单问题,则应该能够通过erl_types:t_from_term/1erl_types:t_is_subtype/2完成所需的大部分工作。

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

答案 1 :(得分:0)

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

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

Msg看起来像var,这是一种完全不透明的类型。您无法判断它是元组还是列表或原子,因为任何人都可以使用为Msg提供的任何内容调用此函数。

这在运行时更容易做到。每次使用!运算符时,您都需要调用包装函数,它会尝试匹配您尝试发送的消息,并在模式匹配时执行其他处理。