1. 程式人生 > >[Erlang 0110] Erlang Abstract Format , Part 1

[Erlang 0110] Erlang Abstract Format , Part 1

  Erlang Abstract Format並不難懂,只是枯燥一點罷了,如果把Abstract Format的文件翻譯出來,其實就是Erlang教科書中語法入門的部分. Erlang Abstract Format實際上是用Erlang程式碼的AST,下面通過一些真切的例項程式碼瞭解一下它的一些細節.

 首先,Erlang Abstract Format裡面包含一些概念,我會在下面的描述中把涉及到的概念字型加粗.請注意概念之間的層次關係.Erlang程式碼本身使用非常扁平的module組織,每一個module是由一系列forms組成的.這些forms分成兩大類:attributes函式宣告

見下圖.

Attribute

attributes相對比較簡單,我們先從一個只有attributes沒有任何函式宣告的module開始

-module(k).
-compile(export_all).
-compile({parse_transform,print_form}).
-export([test/0]).
-url("http://cnblogs.com/me-sa/").
-record(student,{class,id}).
-record(player,{id=0,name=[],level}).
上面程式碼對應的Abstract Format如下:
[{attribute,1,file,{"
k.erl",1}}, {attribute,1,module,k}, {attribute,2,compile,export_all}, {attribute,4,export,[{test,0}]}, {attribute,5,url,"http://cnblogs.com/me-sa/"}, {attribute,6,record, {student,[{record_field,6,{atom,6,class}}, {record_field,
6,{atom,6,id}}]}}, {attribute,7,record, {player,[{record_field,7,{atom,7,id},{integer,7,0}}, {record_field,7,{atom,7,name},{nil,7}}, {record_field,7,{atom,7,level}}]}}, {eof,10}]
   我們可以一一對照上面思維導圖,可以看到上面每一行程式碼包括record定義在內在Abstract Format層面看都是attribute.裡面不斷出現的數字是程式碼所在行數,這個資訊是非常重要的,在編譯時提示程式碼出錯行,執行時報錯包含程式碼行數都要用到它.Erlang最新版報錯的時候已經包含了出錯的程式碼所在行,而之前這個功能是沒有的,工作在蠻荒紀的erlanger,搞了一個smart_exceptions的專案來實現這個功能: https://github.com/thomasl/smart_exceptions/tree/master/stable 言歸正傳,上面需要細說的form是record的定義,在有record欄位初始值的情況,結構會稍複雜一點:{record_field,LINE,Name,Value}.比如player的id=0,對應的Form是{record_field,7,{atom,7,id},{integer,7,0}},id的初始值0是一個字面常量,記作{integer,7,0},在Erlang Abstract Format文件裡面字面常量被稱為Atomic literals,包含四種:atom integer string float,見下圖

Function  declarations

說過了attribute,下面我們看下函式宣告部分,這裡有一個概念Patterns,它特指function或fun的引數列表.我們做幾個簡單的方法出來:
-module(a).
-compile(export_all).
-export([test/0]).
 -record(student,{class,id}).
-record(player,{id=0,name=[],level}).

test()->
  "hello world!".

test(a,[1,2]) ->
  "a:[1,2]";
test(12.5,100)->
   "test".

test([]) ->
    empty;
test(abc) ->
    "atom test".

foo(a)->
   {b,100}.

bar({1,2},12)->
   [1,2,3,4,5,6].

k(Num)  when Num >1000 ->
   bigger_than_100;
k(Num) ->
   whatever.

call(1000)->
  k(1000);
call(1002)->
  erlang:now().       
它對應的Abstract Format是:
[{attribute,1,file,{"a.erl",1}},
        {attribute,1,module,a},
        {attribute,2,compile,export_all},
        {attribute,4,export,[{test,0}]},
        {attribute,6,record,
         {student,
          [{record_field,6,{atom,6,class}},{record_field,6,{atom,6,id}}]}},
        {attribute,7,record,
         {player,
          [{record_field,7,{atom,7,id},{integer,7,0}},
           {record_field,7,{atom,7,name},{nil,7}},
           {record_field,7,{atom,7,level}}]}},
        {function,10,test,0,[{clause,10,[],[],[{string,11,"hello world!"}]}]},
        {function,13,test,2,
         [{clause,13,
           [{atom,13,a},
            {cons,13,{integer,13,1},{cons,13,{integer,13,2},{nil,13}}}],
           [],
           [{string,14,"a:[1,2]"}]},
          {clause,15,
           [{float,15,12.5},{integer,15,100}],
           [],
           [{string,16,"test"}]}]},
        {function,18,test,1,
         [{clause,18,[{nil,18}],[],[{atom,19,empty}]},
          {clause,20,[{atom,20,abc}],[],[{string,21,"atom test"}]}]},
        {function,24,foo,1,
         [{clause,24,
           [{atom,24,a}],
           [],
           [{tuple,25,[{atom,25,b},{integer,25,100}]}]}]},
        {function,27,bar,2,
         [{clause,27,
           [{tuple,27,[{integer,27,1},{integer,27,2}]},{integer,27,12}],
           [],
           [{cons,28,
             {integer,28,1},
             {cons,28,
              {integer,28,2},
              {cons,28,
               {integer,28,3},
               {cons,28,
                {integer,28,4},
                {cons,28,
                 {integer,28,5},
                 {cons,28,{integer,28,6},{nil,28}}}}}}}]}]},
        {function,30,k,1,
         [{clause,30,
           [{var,30,'Num'}],
           [[{op,30,'>',{var,30,'Num'},{integer,30,1000}}]],
           [{atom,31,bigger_than_100}]},
          {clause,32,[{var,32,'Num'}],[],[{atom,33,whatever}]}]},
        {function,36,call,1,
         [{clause,36,
           [{integer,36,1000}],
           [],
           [{call,37,{atom,37,k},[{integer,37,1000}]}]},
          {clause,38,
           [{integer,38,1002}],
           [],
           [{call,39,{remote,39,{atom,39,erlang},{atom,39,now}},[]}]}]},
        {eof,41}]
 看上面的函式宣告,function裡面包含一個或者多個function clauses,比如:
 {function,18,test,1,
         [{clause,18,[{nil,18}],[],[{atom,19,empty}]},
          {clause,20,[{atom,20,abc}],[],[{string,21,"atom test"}]}]},
這段程式碼就很好玩了,它在形式上已經非常貼近Clojure(好吧,Lisp行不行)裡面函式定義的語法了:
Clojure 1.4.0
user=> (defn make_a_set
          ([x] #{x})
          ([x,y] #{x,y}))
#'user/make_a_set
user=> (make_a_set 12)
#{12}
user=> (make_a_set 12 23)
#{12 23}
user=>
再看一下函式呼叫,erlang:now().這一句的呼叫對應的是: [{call,39,{remote,39,{atom,39,erlang},{atom,39,now}},[]}]}]} 看到這裡能想起來Erlang裡面對Local CallRemote Call的定義了吧. 上面的程式碼裡面還可以看到Guard的表達形式:
 [[{op,30,'>',{var,30,'Num'},{integer,30,1000}}]].

  除了function clasuse之外,還有 if clausescase clauses and catch clauses 就不再細說.深入函式體內部就是各種表示式, 方法體裡面包含了一些表示式Expressions,ExpressionsPatterns表示方式是一樣的.比如A = lists:seq(1,10).這樣一個表示式,它的Abstract Format其實很好的解釋了這裡等號其實是一個匹配運算.
7>  E= fun(Code)-> {_,Tokens,_}=erl_scan:string(Code),rp(erl_parse:parse_exprs(Tokens)) end.
#Fun<erl_eval.6.80484245>
8> E(" A = lists:seq(1,10).").
{ok,[{match,1,
            {var,1,'A'},
            {call,1,
                  {remote,1,{atom,1,lists},{atom,1,seq}},
                  [{integer,1,1},{integer,1,10}]}}]}
ok
10> 
除了上面這些"普通"的東西,當然還有列表解析和二進位制資料處理相關的語法,下面通過兩個簡單的例子看下:
Eshell V5.10.2  (abort with ^G)
1>  E= fun(Code)-> {_,Tokens,_}=erl_scan:string(Code),rp(erl_parse:parse_exprs(Tokens)) end.
#Fun<erl_eval.6.80484245>
2> E("[Item || Item<- [1,2,3,4],Item>2 ].").
{ok,[{lc,1,
         {var,1,'Item'},
         [{generate,1,
                    {var,1,'Item'},
                    {cons,1,
                          {integer,1,1},
                          {cons,1,
                                {integer,1,2},
                                {cons,1,{integer,1,3},{cons,1,{integer,1,4},{nil,1}}}}}},
          {op,1,'>',{var,1,'Item'},{integer,1,2}}]}]}
ok
3>
 
7>  E= fun(Code)-> {_,Tokens,_}=erl_scan:string(Code),rp(erl_parse:parse_exprs(Tokens)) end.
#Fun<erl_eval.6.80484245>
8> E("<<A:8,B/binary>> = <<1,2,3,4>>.").
{ok,[{match,1,
            {bin,1,
                 [{bin_element,1,{var,1,'A'},{integer,1,8},default},
                  {bin_element,1,{var,1,'B'},default,[binary]}]},
            {bin,1,
                 [{bin_element,1,{integer,1,1},default,default},
                  {bin_element,1,{integer,1,2},default,default},
                  {bin_element,1,{integer,1,3},default,default},
                  {bin_element,1,{integer,1,4},default,default}]}}]}
ok
對於Erlang資料結構中的王者List需要仔細觀察下,它的表達形式是:
4> E("[a,b,c,d].").
{ok,[{cons,1,
           {atom,1,a},
           {cons,1,
                 {atom,1,b},
                 {cons,1,{atom,1,c},{cons,1,{atom,1,d},{nil,1}}}}}]}
ok
說到這裡我們可以把概念之間的層次關係梳理出來了:

Run! Run!

 上面囉嗦了那麼多細節,那從Abstract Format如何到可執行的程式碼呢?下面我們就完成這個過程:
3>  {ok, MTs, _} = erl_scan:string("-module(t).").
{ok,[{'-',1},
     {atom,1,module},
     {'(',1},
     {atom,1,t},
     {')',1},
     {dot,1}],
    1}
4>  {ok, ETs, _} = erl_scan:string("-export([say/0]).").
{ok,[{'-',1},
     {atom,1,export},
     {'(',1},
     {'[',1},
     {atom,1,say},
     {'/',1},
     {integer,1,0},
     {']',1},
     {')',1},
     {dot,1}],
    1}
5> {ok, FTs, _} = erl_scan:string("say() -> \"hello_world!!\".").
{ok,[{atom,1,say},
     {'(',1},
     {')',1},
     {'->',1},
     {string,1,"hello_world!!"},
     {dot,1}],
    1}
6>  Forms= [begin {ok,R}=erl_parse:parse_form(Item),R end || Item<-[MTs,ETs,FTs]].
[{attribute,1,module,t},
{attribute,1,export,[{say,0}]},
{function,1,say,0,
           [{clause,1,[],[],[{string,1,"hello_world!!"}]}]}]
7> {ok, t, Bin} = compile:forms(Forms).
{ok,t,
    <<70,79,82,49,0,0,1,224,66,69,65,77,65,116,111,109,0,0,0,
      45,0,0,0,5,1,116,...>>}
8> code:load_binary(t,"nofile",Bin).
{module,t}
9> t:say().
"hello_world!!"
10> 
 好吧,好多好玩的東西還沒有說,暫時到這裡,下回繼續...... 最後小圖一張,經歷了25年,13季,大偵探波洛的故事結束了,"女士們,先生們,該收場了........."