[Erlang 0113] Elixir 编译流程梳理

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介:

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

  

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

   

   'Elixir.Kernel.CLI'的代码没有找到?它不是erlang代码不在/lib/elixir/src目录下,它是mx代码,位置在: https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/kernel/cli.ex 经过一系列参数读取解析之后,最终我们可以定位到执行compile的代码位置,如下:
 
 
?
1
2
3
4
5
6
7
8
9
10
11
defmodule Kernel.CLI do
......
   if files != [] do
       wrapper fn ->
         Code.compiler_options(config.compiler_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,代码路径在:  https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/kernel/parallel_compiler.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定义  https://github.com/elixir-lang/elixir/blob/master/lib/elixir/include/elixir.hrl

  

    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#elixir_scope.module),
   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/elixir/m.ex"
       code:load_binary(ModuleName, Listname, Binary),
       Callback(ModuleName, Binary);
     {error, Errors, Warnings} ->
       format_warnings(Bootstrap, Warnings),
       format_errors(Errors)
   end.

 

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

 

?
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的博客,原文链接:

http://www.cnblogs.com/me-sa/p/elixir_compile_step.html如需转载请自行联系原博主。

目录
相关文章
|
3月前
|
安全 API 数据安全/隐私保护
深入浅出python代码混淆:原理与实践
代码混淆就像是给你的代码穿上了一件隐形衣。它可以让你的代码变得难以理解,但并不能完全保证代码的安全。在实际应用中,我们应该将代码混淆作为整个安全策略中的一环,而不是唯一的防线。
|
2月前
|
Python
Python 项目及依赖管理工具技术选型
【8月更文挑战第30天】在进行Python项目及依赖管理时,有多种工具可供选择。虚拟环境工具有`virtualenv`和内置的`venv`,可为项目创建独立环境,避免依赖冲突。依赖管理工具有`pip`、`pipenv`和`poetry`,分别用于安装包、管理依赖并确保版本一致性。选型时需考虑项目需求、团队协作、易用性和社区支持等因素。
54 10
|
3月前
|
存储 测试技术 数据库
Python接口自动化测试框架(练习篇)-- 函数编程(一)
本文通过实际的编程练习,讲解了面向过程编程的概念和应用,包括如何定义函数、处理文件读写以及实现用户注册功能,最终将这些过程封装成函数,体现了Python作为脚本语言的面向过程编程特性。
27 2
|
3月前
|
测试技术 Python
Python接口自动化测试框架(练习篇)-- 函数编程(二)
本文通过具体的编程练习,深入探讨了Python中的函数编程,包括如何定义函数、使用参数和返回值,以及函数式编程的技巧和应用,如使用lambda表达式和递归函数解决实际问题。
27 1
|
3月前
|
IDE 测试技术 开发工具
Python接口自动化测试框架(基础篇)-- 讨厌的异常处理
本文详细讨论了Python中的异常处理机制,包括异常捕获、异常抛出、自定义异常、环境清理,以及使用上下文管理器确保资源正确释放,最后还提到了Python的标准异常类。
37 1
|
3月前
|
安全 搜索推荐 开发者
"揭秘Python编写的艺术境界:不规范代码的悲剧,规范之美让你事半功倍!"
【8月更文挑战第21天】编写高质量Python代码需遵循规范以提升可读性和可维护性。例如,变量命名应采用小写字母加下划线(如`user_name`而非`uName`),函数命名清晰并避免硬编码(如使用`calculate_circle_area`替代`area_of_circle`并定义精确π值)。此外,添加有意义的注释(如解释冒泡排序逻辑),合理排版(如明确函数参数与返回值),以及适当异常处理(确保文件操作安全),都是良好实践。遵循这些规范能显著提高代码质量和团队协作效率。
38 0
|
4月前
|
Rust 测试技术 编译器
Rust与C++的区别及使用问题之Rust项目中组织目录结构的问题如何解决
Rust与C++的区别及使用问题之Rust项目中组织目录结构的问题如何解决
|
3月前
|
开发框架 自然语言处理 Java
跨平台服务开发的利器——深入解析Thrift Compiler的工作机制与内部实现细节!
【8月更文挑战第18天】在现代软件开发中,代码生成器日益重要,能根据特定输入自动生成源代码,提高效率与可维护性。Thrift作为跨平台多语言框架,通过IDL文件定义数据和服务接口,并据此生成多语言代码,涵盖序列化、方法调用等。以示例IDL定义为例,Thrift Compiler生成服务端骨架与客户端代码框架,便于开发者添加业务逻辑。深入源码,“compiler/cpp/src/thriftl”目录下的组件负责词法、语法分析及代码生成,映射IDL至特定语言,体现编译原理与跨语言设计精髓。
61 0
|
3月前
|
测试技术 程序员 开发者
探索代码整洁之道:编写可维护和可扩展的Python程序
【8月更文挑战第3天】在编程的海洋中,我们经常追求的是那些能够高效运行、易于理解和维护的代码。本文将深入探讨如何通过遵循一系列的最佳实践来提升Python代码的整洁度,从而增强其可维护性和可扩展性。我们将通过具体示例,展示如何应用这些原则来编写更优雅、更健壮的Python程序。
35 0
|
6月前
|
缓存 编译器 Go
Build实战指南:优雅编译,高效开发
Build实战指南:优雅编译,高效开发
115 0