注意:目前Elixir版本还不稳定,代码调整较大,本文随时失效
-module(elixir). ...... start_cli() -> application:start(?MODULE), %% start_cli() --> ["+compile","m.ex"] 'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
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
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
-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}]} ]}}]]}}]]}]
-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>
- elixir_aliases的设计
- elixir_scope 的设计
- 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夫人传说。