Abstract Form–動態產生和修改module

來源:互聯網
上載者:User

前面,我們簡單描述了Abstract Form的基本組成。現在,我們來看看如何利用Abstract Form動態產生和修改module。
在介紹Erlang Abstract Form--產生和擷取,已經提到過,要獲得Abstract Form有兩種方法,一種讀取beam檔案中的debug_info,另一種方法就是直接解析原始碼。

提供原始碼文本
修改一個module最有用的功能是增加新的函數。我們從beam檔案可以擷取現有模組的Abstract Form,但是如果需要動態增加方法,最容易想到的就是提供函數的原始碼文本。
解析原始碼通常需要兩個工具,即掃描器和解析器。Erlang提供的基本掃描器是erl_scan,解析器為erl_parse。我們看看它們的文檔。

    MODULE
    erl_scan
    MODULE SUMMARY
    The Erlang Token Scanner
    DESCRIPTION

    This module contains functions for tokenizing characters into Erlang tokens.
    EXPORTS

    string(CharList,StartLine]) -> {ok, Tokens, EndLine} | Error
    string(CharList) -> {ok, Tokens, EndLine} | Error

    Types:

    CharList = string()
    StartLine = EndLine = Line = integer()
    Tokens = [{atom(),Line}|{atom(),Line,term()}]
    Error = {error, ErrorInfo, EndLine}

    Takes the list of characters CharList and tries to scan (tokenize) them. Returns {ok, Tokens, EndLine}, where Tokens are the Erlang tokens from CharList. EndLine is the last line where a token was found.

    StartLine indicates the initial line when scanning starts. string/1 is equivalent to string(CharList,1).

    {error, ErrorInfo, EndLine} is returned if an error occurs. EndLine indicates where the error occurred.

erl_scan:string方法掃描字串文本,如果沒有發生錯誤,則在結果tuple中返回所有的token,不然返回錯誤的行號。
Eshell V5.5 (abort with ^G)
1> c(simplest, [debug_info]).
{ok,simplest}
2> {ok, Tokens, EndLine} = erl_scan:string("test() -> ok.").
{ok,[{atom,1,test},{'(',1},{')',1},{'->',1},{atom,1,ok},{dot,1}],1}
3>

我們傳入一個最簡單函數test,erl_scan:string掃描的結果返回Tokens,其中包含5個token。分別是atom類型的函數名test、左括弧、右括弧、函數頭的結束->、atom類型的atom ok,以及最後結束的dot。

有了這個 Tokens,我們可以用erl_parse解析產生Abstract Form。erl_parse的文檔中說:

    MODULE
    erl_parse
    MODULE SUMMARY
    The Erlang Parser
    DESCRIPTION

    This module is the basic Erlang parser which converts tokens into the abstract form of either forms (i.e., top-level constructs), expressions, or terms. The Abstract Format is described in the ERTS User's Guide. Note that a token list must end with the dot token in order to be acceptable to the parse functions (see erl_scan).
    EXPORTS

    parse_form(Tokens) -> {ok, AbsForm} | {error, ErrorInfo}

    Types:

    Tokens = [Token]
    Token = {Tag,Line} | {Tag,Line,term()}
    Tag = atom()
    AbsForm = term()
    ErrorInfo = see section Error Information below.

    This function parses Tokens as if it were a form. It returns:

    {ok, AbsForm}
        The parsing was successful. AbsForm is the abstract form of the parsed form.
    {error, ErrorInfo}
        An error occurred.

erl_parse可以解析很多種token,包括運算式、term、Form等等,我們需要的是完全解析Form的函數parse_form。同樣,如果解析成功,那麼返回的tuple中將包含tokens代表的Abstract Form,不然返回語法錯誤資訊。

3> erl_parse:parse_form(Tokens).
{ok,{function,1,test,0,[{clause,1,[],[],[{atom,1,ok}]}]}}

我們可以看到,返回結果中包含了函數的Abtract Form。

編譯Abstract Form
有了Abstract Form以後,我們就可以編譯,並載入它。

compile模組的文檔中說:

    forms(Forms)

    Is the same as forms(File, [verbose,report_errors,report_warnings]).

    forms(Forms, Options) -> CompRet

    Types:

    Forms = [Form]
    CompRet = BinRet | ErrRet
    BinRet = {ok,ModuleName,BinaryOrCode} | {ok,ModuleName,BinaryOrCode,Warnings}
    BinaryOrCode = binary() | term() ErrRet = error | {error,Errors,Warnings}

    Analogous to file/1, but takes a list of forms (in the Erlang abstract format representation) as first argument. The option binary is implicit; i.e., no object code file is produced. Options that would ordinarily produce a listing file, such as 'E', will instead cause the internal format for that compiler pass (an Erlang term; usually not a binary) to be returned instead of a binary.

compile:forms這個函數取要編譯的Form作為參數,把它編譯成可以被虛擬機器執行的二進位對象碼資料。它和compile:file基本一樣,只不過提供的是Form參數,而compile:file是需要編譯的檔案名稱。

實際上,compile:file這個方法我們一直都在用。erl的shell中,輸入c(File, options)就是在編譯檔案:

    c(File) -> {ok, Module} | error
    c(File, Options) -> {ok, Module} | error

    Types:

    File = Filename | Module
    Filename = string() | atom()
    Options = [Opt] -- see compile:file/2
    Module = atom()

    c/1,2 compiles and then purges and loads the code for a file. Options defaults to []. Compilation is equivalent to:

    compile:file(File, Options ++ [report_errors, report_warnings])

    Note that purging the code means that any processes lingering in old code for the module are killed without warning. See code/3 for more information.

c調用compile的file方法編譯.erl檔案,然後從記憶體中移去原先存在的代碼,然後載入新的代碼。

要編譯Abstract Form,我們必須提供整個module完整的Form。因此,我們需要提供module屬性、export屬性等等。

1> c(simplest,[debug_info]).
{ok,simplest}
2> {ok, Tokens, EndLine} = erl_scan:string("test() -> ok.").
{ok,[{atom,1,test},{'(',1},{')',1},{'->',1},{atom,1,ok},{dot,1}],1}
3> {ok, Forms} = erl_parse:parse_form(Tokens).
{ok,{function,1,test,0,[{clause,1,[],[],[{atom,1,ok}]}]}}
4>
4> NewForms = [{attribute, 1, module, simplest},{attribute, 2, export, [{test,0}]}, Forms].
[{attribute,1,module,simplest},
{attribute,2,export,[{test,0}]},
{function,1,test,0,[{clause,1,[],[],[{atom,1,ok}]}]}]
5> compile:forms(NewForms).
{ok,simplest,
<<70,79,82,49,0,0,1,188,66,69,65,77,65,116,111,109,0,0,0,55,0,0,0,6,7,...>>}

載入新編譯的對象碼
閱讀上面文檔的另外一個好處就是我們知道編譯以後如何載入。code模組定義了這些函數:

    purge(Module) -> true | false

    Types:

    Module = atom()

    Purges the code for Module, that is, removes code marked as old. If some processes still linger in the old code, these processes are killed before the code is removed.

    Returns true if successful and any process needed to be killed, otherwise false.

purge函數把現有的代碼移去並標記為老版本,如果有任何process在使用舊代碼,那麼這些process將被殺死。注意儘管purge總是成功的,但是它的傳回值只有在任何process需要被殺死的情況下才會返回true.

    load_binary(Module, Filename, Binary) -> {module, Module} | {error, What}

    Types:

    Module = atom()
    Filename = string()
    What = sticky_directory | badarg | term()

    This function can be used to load object code on remote Erlang nodes. It can also be used to load object code where the file name and module name differ. This, however, is a very unusual situation and not recommended. The parameter Binary must contain object code for Module. Filename is only used by the code server to keep a record of from which file the object code for Module comes. Accordingly, Filename is not opened and read by the code server.

    Returns {module, Module} if successful, or {error, sticky_directory} if the object code resides in a sticky directory, or {error, badarg} if any argument is invalid. Also if the loading fails, an error tuple is returned. See erlang:load_module/2 for possible values of What.

load_binary載入編譯好的對象碼,從而使得Module可以被程式使用。如果對象代碼存在於sticky目錄下的話,可能無法成功替換。sticky目錄是erlang自己的運行時系統,包括kernel、stdlib和compiler,為了保證erlang的運行正常,預設情況下這些目錄是受保護的,被認為是sticky的。

組合起來

利用我們前面討論過的內容,我們可以進行完整的實驗.
假設現在有以下程式simplest.erl:

-module(simplest).
-export([foo/0]).
foo() ->
 io:format("foo~n").

我們用erl一步一步進行實驗。

1> c(simplest,[debug_info]).
{ok,simplest}
2> simplest:foo().
foo
ok
3> simplest:test().

=ERROR REPORT==== 18-Aug-2006::15:06:17 ===
Error in process <0.32.0> with exit value: {undef,[{simplest,test,[]},{erl_eval,do_apply,5},{shell,exprs,6},{shell,eval_loop,3}]}

** exited: {undef,[{simplest,test,[]},
               {erl_eval,do_apply,5},
               {shell,exprs,6},
               {shell,eval_loop,3}]} **
4> {ok, Tokens, EndLine} = erl_scan:string("test() -> ok.").
{ok,[{atom,1,test},{'(',1},{')',1},{'->',1},{atom,1,ok},{dot,1}],1}
5> {ok, Forms} = erl_parse:parse_form(Tokens).
{ok,{function,1,test,0,[{clause,1,[],[],[{atom,1,ok}]}]}}
6> NewForms = [{attribute, 1, module, simplest},{attribute, 2, export, [{test,0}]}, Forms].
[{attribute,1,module,simplest},
{attribute,2,export,[{test,0}]},
{function,1,test,0,[{clause,1,[],[],[{atom,1,ok}]}]}]
7> {ok,simplest,Binary} = compile:forms(NewForms).
{ok,simplest,
<<70,79,82,49,0,0,1,188,66,69,65,77,65,116,111,109,0,0,0,56,0,0,0,6,8,...>>}
8> code:purge(simplest).
false
9> code:load_binary(simplest,"simplest.erl",Binary).
{module,simplest}
10>  simplest:foo().

=ERROR REPORT==== 18-Aug-2006::15:08:13 ===
Error in process <0.40.0> with exit value: {undef,[{simplest,foo,[]},{erl_eval,do_apply,5},{shell,exprs,6},{shell,eval_loop,3}]}

** exited: {undef,[{simplest,foo,[]},
               {erl_eval,do_apply,5},
               {shell,exprs,6},
               {shell,eval_loop,3}]} **
11> simplest:test().
ok
12>
結果是新加入了一個export的test/0函數,原先的foo/0函數沒有了。

 

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/minyskirt/archive/2009/12/13/4996838.aspx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.