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

     之前简单演示过如何从elixir ex代码生成并运行Erlang代码,下面仔细梳理一遍elixir文件的编译过程,书接上文,从elixir的代码切入,这一次我们主要关注编译流程,一些细节暂时不展开.
 
 
 
-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的代码位置,如下:
 
 
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).
 
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我们输出一下看看:
 
 
-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).

  

 
[{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

-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包装了一下,之前的版本简单直接,可以先看一下:

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是这样的:

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,等等)进行组合.

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)方法了:

 
%% 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编译自动生成的模块,你可以使用下面的方法拿到它的代码:

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夫人传说。

 
 

[Erlang 0113] Elixir 编译流程梳理的更多相关文章

  1. .16-浅析webpack源码之编译后流程梳理

    这节把编译打包后的流程梳理一下,然后集中处理compile. 之前忽略了一个点,如下: new NodeEnvironmentPlugin().apply(compiler); // 引入插件加载 i ...

  2. [Erlang 0108] Elixir 入门

    Erlang Resources里面关于Elixir的资料越来越多,加上Joe Armstrong的这篇文章,对Elixir的兴趣也越来越浓厚,投入零散时间学习了一下.零零散散,测试代码写了一些,Ev ...

  3. Erlang 和 Elixir 互相调用 (转)

    lixr设计目标之一就是要确保兼容性,可以兼容Erlang和其生态系统.Elixir和Erlang 都是运行同样的虚拟机平台(Erlang Virtual Machine).不管是在Erlang使用E ...

  4. Erlang 和 Elixir的差异

    原文: http://elixir-lang.org/crash-course.html 函数调用 Elixir允许你调用函数的时候省略括号, Erlang不行. Erlang Elixir some ...

  5. CentOS 7.7安装Erlang和Elixir

    安装之前,先看一下它们的简要说明 Erlang Erlang是一种开源编程语言,用于构建对高可用性有要求的大规模可扩展的软实时系统.它通常用于电信,银行,电子商务,计算机电话和即时消息中.Erlang ...

  6. 从一次编译出发梳理概念: Jetty,Jersey,hk2,glassFish,Javax,Jakarta

    从一次编译出发梳理概念: Jetty,Jersey,hk2,glassFish,Javax,Jakarta 目录 从一次编译出发梳理概念: Jetty,Jersey,hk2,glassFish,Jav ...

  7. zookeeper心跳机制流程梳理

    zookeeper心跳机制流程梳理 Processor链Chain protected void setupRequestProcessors() { RequestProcessor finalPr ...

  8. Gcc的编译流程分为了四个步骤:

    http://blog.csdn.net/xiaohouye/article/details/52084770(转) Gcc的编译流程分为了四个步骤: 1.预处理,生成预编译文件(.文件): Gcc ...

  9. 编译流程,C开发常见文件类型名

    编译流程 我们常说的编译是一个整体的概念,是指从源程序到可执行程序的整个过程,实际上,C语言编译的过程可以进一步细分为预编译->编译->汇编->链接 预编译是把include关键字所 ...

随机推荐

  1. 记一次Redis被攻击的事件

    最近几个月非常忙,所以很少有时间写博客,这几天终于闲了一些,于是就在整理平时的一些笔记.恰好这几天Redis服务器发生了问题,就记录一下. 我司有两款分别是2B和2C的App,类似于阿里旺旺的卖家版和 ...

  2. hibernate一对一外键单向关联

    关联是类(类的实例)之间的关系,表示有意义和值得关注的连接. 本系列将介绍Hibernate中主要的几种关联映射 Hibernate一对一主键单向关联Hibernate一对一主键双向关联Hiberna ...

  3. C#设计模式-中介者模式

    在现实生活中,有很多中介者模式的身影,例如QQ游戏平台,聊天室.QQ群和短信平台,这些都是中介者模式在现实生活中的应用,下面就具体分享下我对中介者模式的理解. 一. 中介者(Mediator)模式 从 ...

  4. Memcached和Redis比较

    一.存储 Memcached基本只支持简单的key-value存储方式.Redis除key-value之外,还支持list,set,sorted set,hash等数据结构:Redis支持数据的备份, ...

  5. Hibernate整合Spring异常'sessionFactory' or 'hibernateTemplate' is required

    今日在写GenericDao时,发现了一个异常,内容如下: org.springframework.beans.factory.BeanCreationException: Error creatin ...

  6. node入门学习1

    一个普通网站访问过程(1)浏览器向服务器发出一个HTTP请求(2)域名解析为IP地址(3)建立TCP连接(4)浏览器发起HTTP请求(5)服务器接收到HTTP请求,解析请求的路径和参数 出现乱码的原因 ...

  7. java https单向认证(忽略认证)并支持http基本认证

    https单向认证(忽略认证)并支持http基本认证, 温馨提示 1,jar包要导入对 2,有匿名类编译要注意 3,欢迎提问,拿走不谢!背景知识 Https访问的相关知识中,主要分为单向验证和双向验证 ...

  8. leetcode--5. Longest Palindromic Substring

    题目来自 https://leetcode.com/problems/longest-palindromic-substring/ 题目:Given a string S, find the long ...

  9. 理解 Neutorn LBaaS - 每天5分钟玩转 OpenStack(120)

    Load Balance as a Service(LBaaS)是 Neutron 提供的一项高级网络服务.LBaaS 允许租户在自己的网络中创建和管理 load balancer. load bal ...

  10. 【分布式】Zookeeper会话

    一.前言 前面分析了Zookeeper客户端的细节,接着继续学习Zookeeper中的一个非常重要的概念:会话. 二.会话 客户端与服务端之间任何交互操作都与会话息息相关,如临时节点的生命周期.客户端 ...