IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [原][Erlang 0111] Erlang Abstract Format , Part 2

    ligaorenvip发表于 2014-09-12 10:13:24
    love 0


    上回书,我们说到飞天玉虎蒋伯芳来到蜈蚣岭,不是,重来,上回咱们说到可以在Erlang Shell里面手工构造,加载并调用一个模块.在那个demo里面,我把多个Form单独生成出来,最后放在一起做compile:forms,是不是可以简单点?我们先看完整的module代码,erl_scan:string之后是什么样子的:

    erl_syntax

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    Eshell V5.10.2 (abort with ^G)
    1> Code = "-module(t).\n-export([say/0]).\n\nsay() -> \"Hello world!!\". ".
    "-module(t).\n-export([say/0]).\n\nsay() -> \"Hello world!!\". "
    2> {ok,Tokens,_} =erl_scan:string(Code).
    {ok,[{'-',1},
    {atom,1,module},
    {'(',1},
    {atom,1,t},
    {')',1},
    {dot,1},
    {'-',2},
    {atom,2,export},
    {'(',2},
    {'[',2},
    {atom,2,say},
    {'/',2},
    {integer,2,0},
    {']',2},
    {')',2},
    {dot,2},
    {atom,4,say},
    {'(',4},
    {')',4},
    {'->',4},
    {string,4,"Hello world!!"},
    {dot,4}],
    4}

      

    可以看到上面的list里面包含了若干Form,erl_scan可以逐行解析代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    4> erl_scan:tokens([],Code,1).
    {done,{ok,[{'-',1},
    {atom,1,module},
    {'(',1},
    {atom,1,t},
    {')',1},
    {dot,1}],
    2},
    "-export([say/0]).\n\nsay() -> \"Hello world!!\". "}

      

    当然还有更简单的方式,dynamic_compile 项目把这些事情都做了,还考虑了更为复杂的情况,比如头文件,宏,注释,record之类的, https://github.com/JacobVorreuter/dynamic_compile/blob/master/src/dynamic_compile.erl 下面就是dynamic_compile使用的一个例子:
    1
    2
    3
    4
    5
    6
    7
    8
    Eshell V5.10.2 (abort with ^G)
    1> Code = "-module(t).\n-export([say/0]).\n\nsay() -> \"Hello world!!\". ".
    "-module(t).\n-export([say/0]).\n\nsay() -> \"Hello world!!\". "
    2> dynamic_compile:load_from_string(Code).
    {module,t}
    3> t:say().
    "Hello world!!"
    4>

      

    上面拼字符串的方法看起来丑?你可以选择erl_syntax,下面我们用比较"优雅"的方法去创建t模块:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    Eshell V5.10.2 (abort with ^G)
    1> M = erl_syntax:attribute(erl_syntax:atom(module),[erl_syntax:atom(t)]).
    {tree,attribute,
    {attr,0,[],none},
    {attribute,{tree,atom,{attr,0,[],none},module},
    [{tree,atom,{attr,0,[],none},t}]}}
    2> MF = erl_syntax:revert(M).
    {attribute,0,module,t}
    3> E = erl_syntax:attribute(erl_syntax:atom(export),[erl_syntax:list([erl_syntax:arity_qualifier(erl_syntax:atom(say),erl_syntax:integer(0))])]).
    {tree,attribute,
    {attr,0,[],none},
    {attribute,
    {tree,atom,{attr,0,[],none},export},
    [{tree,list,
    {attr,0,[],none},
    {list,
    [{tree,arity_qualifier,
    {attr,0,[],none},
    {arity_qualifier,
    {tree,atom,{attr,0,[],none},say},
    {tree,integer,{attr,0,[],none},0}}}],
    none}}]}}
    4> ExportForm = erl_syntax:revert(E).
    {attribute,0,export,[{say,0}]}
    5>
    5>
    5> Clause= erl_syntax:clause([],[],[erl_syntax:atom(hello_world)]).
    {tree,clause,
    {attr,0,[],none},
    {clause,[],none,[{tree,atom,{attr,0,[],none},hello_world}]}}
    6>
    6> Function = erl_syntax:function(erl_syntax:atom(say),[Clause]).
    {tree,function,
    {attr,0,[],none},
    {func,{tree,atom,{attr,0,[],none},say},
    [{tree,clause,
    {attr,0,[],none},
    {clause,[],none,
    [{tree,atom,{attr,0,[],none},hello_world}]}}]}}
    7> FunctionForm = erl_syntax:revert(Function).
    {function,0,say,0,[{clause,0,[],[],[{atom,0,hello_world}]}]}
    8> {ok, Mod, Bin1} = compile:forms([MF,ExportForm, FunctionForm]).
    {ok,t,
    <<70,79,82,49,0,0,1,208,66,69,65,77,65,116,111,109,0,0,0,
    57,0,0,0,6,1,116,...>>}
    9> t:say().
    ** exception error: undefined function t:say/0
    10> code:load_binary(Mod, [], Bin1).
    {module,t}
    11> t:say().
    hello_world
    12>

      

    Erlang Shared Data using mochiglobal 里面mochiglobal 就是用这种方法实现的,不过,不过,又会有人提意见了,这编写难度也太大了,能折中一下吗?好吧,请出下一位嘉宾smerl
    我曾经介绍过开源项目smerl,其定位就是Simple Metaprogramming for Erlang, 我们可以从这份代码里面学到erl_scan erl_parse erl_eval更灵活的应用,项目地址:http://code.google.com/p/smerl/
    1
    2
    3
    4
    5
    6
    7
    test_smerl() ->
    M1 = smerl:new(foo),
    {ok, M2} = smerl:add_func(M1, "bar() -> 1 + 1."),
    smerl:compile(M2),
    foo:bar(), % returns 2``
    smerl:has_func(M2, bar, 0). % returns true

      

    parse_transform

    在上篇文章里面,我需要反复生成 Abstract Format,开始手工搞了一下,后来不胜其烦就使用了一下parse_transform.这东西是干什么用的呢?
    {parse_transform,Module}
    Causes the parse transformation function Module:parse_transform/2 to be applied to the parsed code before the code is checked for errors.
    对,它就是允许你在检查错误之前插入自己的逻辑,把那些"奇怪的东西"变成正常的东西,当然你可以选择什么都不做,仅仅打印,看代码:
    1
    2
    3
    4
    5
    6
    -module(print_form).
    -export([parse_transform/2]).
    parse_transform(Forms, _Options) ->
    io:format("forms: ~p~n", [Forms]),
    Forms.

      

    下面,我们写一个简单的模块a.erl,然后编译它,看输出:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [root@nimbus demo]# cat a.erl
    -module(a).
    -compile({parse_transform,print_form}).
    -export([test/0]).
    test()->
    "hello world!".
    [root@nimbus demo]# erlc -o . -pa . a.erl
    forms: [{attribute,1,file,{"a.erl",1}},
    {attribute,1,module,a},
    {attribute,3,export,[{test,0}]},
    {function,5,test,0,[{clause,5,[],[],[{string,6,"hello world!"}]}]},
    {eof,10}]

      

    好吧,感冒,鼻子堵得难受,先到这里吧
    参考资料:
    [0] erl_syntax http://erlang.org/doc/man/erl_syntax.html
    [1] Erlang: How to Create and Compile Module in Run-time
    http://vas.io/blog/2013/03/23/erlang-how-to-create-and-compile-module-in-run-time/
    [2] Hacking-Erlang http://zh.scribd.com/doc/22451864/Hacking-Erlang
    最后小图一张,最早在山东卫视凌晨剧场看过第一季,现在已经14季了, 老员工只有Nicky ,Sara ,Jim了:

    上回书,我们说到飞天玉虎蒋伯芳来到蜈蚣岭,不是,重来,上回咱们说到可以在Erlang Shell里面手工构造,加载并调用一个模块.在那个demo里面,我把多个Form单独生成出来,最后放在一起做compile:forms,是不是可以简单点?我们先看完整的module代码,erl_scan:string之后是什么样子的:

    erl_syntax

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    Eshell V5.10.2 (abort with ^G)
    1> Code = "-module(t).\n-export([say/0]).\n\nsay() -> \"Hello world!!\". ".
    "-module(t).\n-export([say/0]).\n\nsay() -> \"Hello world!!\". "
    2> {ok,Tokens,_} =erl_scan:string(Code).
    {ok,[{'-',1},
    {atom,1,module},
    {'(',1},
    {atom,1,t},
    {')',1},
    {dot,1},
    {'-',2},
    {atom,2,export},
    {'(',2},
    {'[',2},
    {atom,2,say},
    {'/',2},
    {integer,2,0},
    {']',2},
    {')',2},
    {dot,2},
    {atom,4,say},
    {'(',4},
    {')',4},
    {'->',4},
    {string,4,"Hello world!!"},
    {dot,4}],
    4}

      

    可以看到上面的list里面包含了若干Form,erl_scan可以逐行解析代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    4> erl_scan:tokens([],Code,1).
    {done,{ok,[{'-',1},
    {atom,1,module},
    {'(',1},
    {atom,1,t},
    {')',1},
    {dot,1}],
    2},
    "-export([say/0]).\n\nsay() -> \"Hello world!!\". "}

      

    当然还有更简单的方式,dynamic_compile 项目把这些事情都做了,还考虑了更为复杂的情况,比如头文件,宏,注释,record之类的, https://github.com/JacobVorreuter/dynamic_compile/blob/master/src/dynamic_compile.erl 下面就是dynamic_compile使用的一个例子:
    1
    2
    3
    4
    5
    6
    7
    8
    Eshell V5.10.2 (abort with ^G)
    1> Code = "-module(t).\n-export([say/0]).\n\nsay() -> \"Hello world!!\". ".
    "-module(t).\n-export([say/0]).\n\nsay() -> \"Hello world!!\". "
    2> dynamic_compile:load_from_string(Code).
    {module,t}
    3> t:say().
    "Hello world!!"
    4>

      

    上面拼字符串的方法看起来丑?你可以选择erl_syntax,下面我们用比较"优雅"的方法去创建t模块:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    Eshell V5.10.2 (abort with ^G)
    1> M = erl_syntax:attribute(erl_syntax:atom(module),[erl_syntax:atom(t)]).
    {tree,attribute,
    {attr,0,[],none},
    {attribute,{tree,atom,{attr,0,[],none},module},
    [{tree,atom,{attr,0,[],none},t}]}}
    2> MF = erl_syntax:revert(M).
    {attribute,0,module,t}
    3> E = erl_syntax:attribute(erl_syntax:atom(export),[erl_syntax:list([erl_syntax:arity_qualifier(erl_syntax:atom(say),erl_syntax:integer(0))])]).
    {tree,attribute,
    {attr,0,[],none},
    {attribute,
    {tree,atom,{attr,0,[],none},export},
    [{tree,list,
    {attr,0,[],none},
    {list,
    [{tree,arity_qualifier,
    {attr,0,[],none},
    {arity_qualifier,
    {tree,atom,{attr,0,[],none},say},
    {tree,integer,{attr,0,[],none},0}}}],
    none}}]}}
    4> ExportForm = erl_syntax:revert(E).
    {attribute,0,export,[{say,0}]}
    5>
    5>
    5> Clause= erl_syntax:clause([],[],[erl_syntax:atom(hello_world)]).
    {tree,clause,
    {attr,0,[],none},
    {clause,[],none,[{tree,atom,{attr,0,[],none},hello_world}]}}
    6>
    6> Function = erl_syntax:function(erl_syntax:atom(say),[Clause]).
    {tree,function,
    {attr,0,[],none},
    {func,{tree,atom,{attr,0,[],none},say},
    [{tree,clause,
    {attr,0,[],none},
    {clause,[],none,
    [{tree,atom,{attr,0,[],none},hello_world}]}}]}}
    7> FunctionForm = erl_syntax:revert(Function).
    {function,0,say,0,[{clause,0,[],[],[{atom,0,hello_world}]}]}
    8> {ok, Mod, Bin1} = compile:forms([MF,ExportForm, FunctionForm]).
    {ok,t,
    <<70,79,82,49,0,0,1,208,66,69,65,77,65,116,111,109,0,0,0,
    57,0,0,0,6,1,116,...>>}
    9> t:say().
    ** exception error: undefined function t:say/0
    10> code:load_binary(Mod, [], Bin1).
    {module,t}
    11> t:say().
    hello_world
    12>

      

    Erlang Shared Data using mochiglobal 里面mochiglobal 就是用这种方法实现的,不过,不过,又会有人提意见了,这编写难度也太大了,能折中一下吗?好吧,请出下一位嘉宾smerl
    我曾经介绍过开源项目smerl,其定位就是Simple Metaprogramming for Erlang, 我们可以从这份代码里面学到erl_scan erl_parse erl_eval更灵活的应用,项目地址:http://code.google.com/p/smerl/
    1
    2
    3
    4
    5
    6
    7
    test_smerl() ->
    M1 = smerl:new(foo),
    {ok, M2} = smerl:add_func(M1, "bar() -> 1 + 1."),
    smerl:compile(M2),
    foo:bar(), % returns 2``
    smerl:has_func(M2, bar, 0). % returns true

      

    parse_transform

    在上篇文章里面,我需要反复生成 Abstract Format,开始手工搞了一下,后来不胜其烦就使用了一下parse_transform.这东西是干什么用的呢?
    {parse_transform,Module}
    Causes the parse transformation function Module:parse_transform/2 to be applied to the parsed code before the code is checked for errors.
    对,它就是允许你在检查错误之前插入自己的逻辑,把那些"奇怪的东西"变成正常的东西,当然你可以选择什么都不做,仅仅打印,看代码:
    1
    2
    3
    4
    5
    6
    -module(print_form).
    -export([parse_transform/2]).
    parse_transform(Forms, _Options) ->
    io:format("forms: ~p~n", [Forms]),
    Forms.

      

    下面,我们写一个简单的模块a.erl,然后编译它,看输出:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [root@nimbus demo]# cat a.erl
    -module(a).
    -compile({parse_transform,print_form}).
    -export([test/0]).
    test()->
    "hello world!".
    [root@nimbus demo]# erlc -o . -pa . a.erl
    forms: [{attribute,1,file,{"a.erl",1}},
    {attribute,1,module,a},
    {attribute,3,export,[{test,0}]},
    {function,5,test,0,[{clause,5,[],[],[{string,6,"hello world!"}]}]},
    {eof,10}]

      

    好吧,感冒,鼻子堵得难受,先到这里吧
    参考资料:
    [0] erl_syntax http://erlang.org/doc/man/erl_syntax.html
    [1] Erlang: How to Create and Compile Module in Run-time
    http://vas.io/blog/2013/03/23/erlang-how-to-create-and-compile-module-in-run-time/
    [2] Hacking-Erlang http://zh.scribd.com/doc/22451864/Hacking-Erlang
    最后小图一张,最早在山东卫视凌晨剧场看过第一季,现在已经14季了, 老员工只有Nicky ,Sara ,Jim了:



沪ICP备19023445号-2号
友情链接