[Erlang 0113] Elixir 编译流程梳理

阅读: 评论:0

[Erlang 0113] Elixir 编译流程梳理

[Erlang 0113] Elixir 编译流程梳理

  注意:目前Elixir版本还不稳定,代码调整较大,本文随时失效

  

之前简单演示过如何从elixir ex代码生成并运行Erlang代码,下面仔细梳理一遍elixir文件的编译过程, 书接上文,从elixir的代码切入,这一次我们主要关注编译流程,一些细节暂时不展开.   ?
1 2 3 4 5 6 -module(elixir). ...... start_cli() ->    application:start(?MODULE),    %% start_cli() --> [ "+compile" , &#" ]    'Elixir.Kernel.CLI' :main(init:get_plain_arguments()).

   

'Elixir.Kernel.CLI'的代码没有找到?它不是erlang代码不在/lib/elixir/src目录下,它是mx代码,位置在: .ex 经过一系列参数读取解析之后,最终我们可以定位到执行compile的代码位置,如下: ?
1 2 3 4 5 6 7 8 9 10 11 defmodule Kernel.CLI do ......    if files != [] do        wrapper fn ->          Codepiler_options(configpiler_options)          Kernel.ParallelCompiler.files_to_path(files, config.output,            each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)        end      else        { :error, "--compile : No files matched patterns #{Enum.join(patterns, " , ")}" }      end

  

这里调用了同一目录下面的Kernel.ParallelCompiler的方法,注意由于Elixir对于代码模块名和文件名没有一致性的要求,找代码的时候要注意一点;对应的ex代码模块是parallel_compile,代码路径在:  .ex 从这里开始,执行逻辑就回到了Erlang代码,:elixir_compiler.file(h)等价的Erlang调用代码是elixir_compile:file(h).   ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 defmodule Kernel.ParallelCompiler do .....    try do          if output do            :elixir_compiler.file_to_path(h, output)          else            :elixir_compiler.file(h)          end          parent <- { :compiled, self(), h }        catch          kind, reason ->            parent <- { :failure, self(), kind, reason, System.stacktrace }        end

 

elixir_compiler 完成路径解析之后,最终调用了string(Contents, File)方法.我们将string(Contents, File)方法逐步拆解开,elixir_translator:'forms!'(Contents, 1, File, [])首先通过elixir_tokenizer:tokenize 将代码文本解析成为Tokens,然后通过 Forms = elixir_parser:parse(Tokens) 解析成为Elixir AST,注意这里还不是Erlang Abstract Format,这里的Forms我们输出一下看看: ?
1 2 3 4 5 6 7 8 9 10 11 12 -module(elixir_compiler).    file(Relative) when is_binary(Relative) ->    File = filename:absname(Relative),    { ok, Bin } = file:read_file(File),    string (elixir_utils:characters_to_list(Bin), File).    string (Contents, File) when is_list(Contents), is_binary(File) ->    Forms = elixir_translator: 'forms!' (Contents, 1, File, []),    quoted(Forms, File).

  

?
1 2 3 4 5 6 7 8 9 10 11 [{defmodule,     [{line,1}],     [{ '__aliases__' ,[{line,1}],[ 'Math' ]},      [{ do ,        {def,[{line,2}],[{sum,[{line,2}],[{a,[{line,2}],nil},{b,[{line,2}],nil}]},          [{ do ,{ '__block__' ,[],[           { '=' ,[{line,3}],[{a,[{line,3}],nil},123]},           { '=' ,[{line,4}],[{a,[{line,4}],nil},2365]},           { '+' ,[{line,5}],[            {a,[{line,5}],nil},{b,[{line,5}],nil}]}            ]}}]]}}]]}]

  

Elixir AST在quoted(Forms, File)函数完成到Erlang Abstract Format Forms的转换,下面我们跟进quoted(Forms,File)方法,在最近的版本中lexical_tracker加入了Scope中;Scope维护了代码的上下文信息,我们暂时有这样一个印象即可,我们先把流程走完,下面就要深入eval_forms(Forms, Line, Vars, S)方法了. 附Scpoe定义  .hrl

+ View Code ?
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 -record(elixir_scope, {    context=nil,             %% can be assign, guards or nil    extra=nil,               %% extra information about the context, like fn_match for fns    noname= false ,            %% when true , don't add new names (used by try )    super= false ,             %% when true , it means super was invoked    caller= false ,            %% when true , it means caller was invoked    module=nil,              %% the current module    function=nil,            %% the current function    vars=[],                 %% a dict of defined variables and their alias    backup_vars=nil,         %% a copy of vars to be used on ^ var    temp_vars=nil,           %% a set of all variables defined in a particular assign    clause_vars=nil,         %% a dict of all variables defined in a particular clause    extra_guards=nil,        %% extra guards from args expansion    counter=[],              %% a counter for the variables defined    local=nil,               %% the scope to evaluate local functions against    context_modules=[],      %% modules defined in the current context    macro_aliases=[],        %% keep aliases defined inside a macro    macro_counter=0,         %% macros expansions counter    lexical_tracker=nil,     %% holds the lexical tracker pid    aliases,                 %% an orddict with aliases by new -> old names    file,                    %% the current scope filename    requires,                %% a set with modules required    macros,                  %% a list with macros imported from module    functions                %% a list with functions imported from module }).

  

    quoted方法最近的变化是使用elixir_lexical:run包装了一下,之前的版本简单直接,可以先看一下:

 

?
1 2 3 4 5 6 7 8 9 10 quoted(Forms, File) when is_binary(File) ->    Previous = get (elixir_compiled), % M:elixir_compiler Previous undefined    try      put(elixir_compiled, []),      eval_forms(Forms, 1, [], elixir:scope_for_eval([{file,File}])),      lists:reverse( get (elixir_compiled))    after      put(elixir_compiled, Previous)    end.

 

  现在quoted是这样的:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 quoted(Forms, File) when is_binary(File) ->    Previous = get (elixir_compiled),    try      put(elixir_compiled, []),      elixir_lexical:run(File, fun        (Pid) ->          Scope = elixir:scope_for_eval([{file,File}]),          eval_forms(Forms, 1, [], Scope#elixir_scope{lexical_tracker=Pid})      end),      lists:reverse( get (elixir_compiled))    after      put(elixir_compiled, Previous)    end.

 

   quoted方法里面我们需要重点关注的是eval_forms方法,在这个方法里面完成了Elixir AST到Erlang AST转换,Elixir表达式通过 elixir_translator:translate被翻译成对应的Erlang Abstract Format.之后eval_mod(Fun, Exprs, Line, File, Module, Vars)完成对表达式和代码其它部分(比如attribute,等等)进行组合.

 

?
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 eval_forms(Forms, Line, Vars, S) ->    { Module, I } = retrieve_module_name(),    { Exprs, FS } = elixir_translator:translate(Forms, S),    Fun  = eval_fun(S#dule),    Form = eval_mod(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),    Args = list_to_tuple([V || { _, V } <- Vars]),    %% Pass { native, false } to speed up bootstrap    %% process when native is set to true    { module(Form, S#elixir_scope.file, [{native, false }], true ,      fun(_, Binary) ->      Res = Module:Fun(Args),      code:delete(Module),      %% If we have labeled locals, anonymous functions      %% were created and therefore we cannot ditch the      %% module      case beam_lib:chunks(Binary, [labeled_locals]) of        { ok, { _, [{ labeled_locals, []}] } } ->          code:purge(Module),          return_module_name(I);        _ ->          ok      end,      Res    end), FS }.

  

  最后完成编译和加载的重头戏就在module(Forms, File, Opts, Callback)方法了:

?
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 %% Compile the module by forms based on the scope information %% executes the callback in case of success. This automatically %% handles errors and warnings. Used by this module and elixir_module. module(Forms, File, Opts, Callback) ->    DebugInfo = (get_opt(debug_info) == true ) orelse lists:member(debug_info, Opts),    Final =      if DebugInfo -> [debug_info];         true -> []      end,    module(Forms, File, Final, false , Callback). module(Forms, File, RawOptions, Bootstrap, Callback) when      is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->    { Options, SkipNative } = compile_opts(Forms, RawOptions),    Listname = elixir_utils:characters_to_list(File),    case compile:noenv_forms([no_auto_import()|Forms], [ return ,{source,Listname}|Options]) of      {ok, ModuleName, Binary, RawWarnings} ->        Warnings = case SkipNative of          true  -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];          false -> RawWarnings        end,        format_warnings(Bootstrap, Warnings),        %%%% ModuleName : 'Elixir.Math' ListName: "/data2/"        code:load_binary(ModuleName, Listname, Binary),        Callback(ModuleName, Binary);      {error, Errors, Warnings} ->        format_warnings(Bootstrap, Warnings),        format_errors(Errors)    end.

 

  到这里编译的流程已经走完,附上几个可能会用到的资料,首先是elixir_parser:parse(Tokens)的代码,你可能会奇怪这个模块的代码在哪里?这个是通过l编译自动生成的模块,你可以使用下面的方法拿到它的代码:

 

?
1 2 3 4 5 6 7 8 9 10 11 Eshell V5.10.2 (abort with ^G) 1> {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks( "elixir_parser" ,[abstract_code]). {ok,{elixir_parser, [{abstract_code, ........ {...}|...]}}]}} 2> Dump= fun(Content)-> file:write_file( "/data/dump.data" , io_lib:fwrite( "~ts.n" , [Content])) end. #Fun<erl_eval.6.80484245> 3> Dump(erl_prettypr:format(erl_syntax:form_list(AC))). ok 4>

   

下一步,就要深入几个非常有意思的细节了 %% TODO
  1. elixir_aliases的设计
  2. elixir_scope 的设计
  3. elixir macro 相关的几个话题:hygiene unquote_splicing
今天先到这里. 最后,小图一张:

马背上的Godiva夫人


主人公:戈黛娃夫人Lady Godiva,或称Godgifu,约990年—1067年9月10日
作者:约翰·柯里尔(John Collier)所绘,约1898年

据说大约在1040年,统治考文垂(Coventry)城市的Leofric the Dane伯爵决定向人民征收重税,支持军队出战,令人民的生活苦不堪言。伯爵善良美丽的妻子Godiva夫人眼见民生疾苦,决定恳求伯爵减收徵税,减轻人民的负担。Leofric伯爵勃然大怒,认为Godiva夫人为了这班爱哭哭啼啼的贱民苦苦衷求,实在丢脸。Godiva夫人却回答说伯爵定会发现这些人民是多么可敬。他们决定打赌——Godiva夫人要赤裸身躯骑马走过城中大街,仅以长发遮掩身体,假如人民全部留在屋内,不偷望Godiva夫人的话,伯爵便会宣布减税。翌日早上,Godiva夫人骑上马走向城中,Coventry市所有百姓都诚实地躲避在屋内,令大恩人不至蒙羞。事后,Leofric伯爵信守诺言,宣布全城减税。这就是著名的Godiva夫人传说。

本文转自博客园坚强2002的博客,原文链接:

.html如需转载请自行联系原博主。

本文发布于:2024-01-29 12:01:31,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170650089415133.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:流程   Erlang   Elixir
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23