Current section
Files
Jump to
Current section
Files
lib/elixir_script/translator/module.ex
defmodule ElixirScript.Translator.Module do
@moduledoc false
alias ESTree.Tools.Builder, as: JS
alias ElixirScript.Translator
alias ElixirScript.Translator.Utils
alias ElixirScript.Translator.Function
def make_module(ElixirScript.Temp, body, env) do
{ body, _ } = translate_body(body, env)
%{ name: ElixirScript.Temp, body: body |> Utils.inflate_groups }
end
def make_module(module, nil, _) do
%{ name: module, body: [] }
end
def make_module(module, body, env) do
body = process_use(body)
{ body, functions } = extract_functions_from_module(body)
{ body, env } = translate_body(body, env)
{ exported_functions, private_functions } = process_functions(functions, env)
module_refs = ElixirScript.Translator.State.get_module_references(module) -- [env.module]
{imports, body} = extract_imports_from_body(body)
{structs, body} = extract_structs_from_body(body, env)
imports = imports ++ make_std_lib_import() ++ make_imports(module_refs)
#Collect all the functions so that we can process their arity
body = Enum.map(body, fn(x) ->
case x do
%ESTree.CallExpression{} ->
JS.expression_statement(x)
_ ->
x
end
end)
body = Utils.inflate_groups(body)
exported_object = JS.object_expression(
make_defstruct_property(module, structs) ++
Enum.map(exported_functions, fn({key, _value}) ->
JS.property(JS.identifier(Utils.filter_name(key)), JS.identifier(Utils.filter_name(key)), :init, true)
end)
)
exported_functions = Enum.map(exported_functions, fn({_key, value}) -> value end)
private_functions = Enum.map(private_functions, fn({_key, value}) -> value end)
default = JS.export_default_declaration(exported_object)
result = %{
name: Utils.quoted_to_name({:__aliases__, [], module }),
body: imports ++ structs ++ private_functions ++ exported_functions ++ body ++ [default]
}
result
end
defp process_use(ast) do
Macro.prewalk(ast, &do_process_use(&1))
end
defp do_process_use({:use, _, _} = ast) do
ast
|> Macro.expand(ElixirScript.Translator.State.get().compiler_opts.env)
|> expand__using__
end
defp do_process_use(ast) do
ast
end
defp expand__using__({:__block__, _, [{:require, _, _}, {{:., _, [_, :__using__]}, _, _} = using_ast]}) do
Macro.expand_once(using_ast, ElixirScript.Translator.State.get().compiler_opts.env)
end
defp expand__using__({:__block__, _, [{:__block__, _, [{:require, _, _}, {{:., _, [_, :__using__]}, _, _} = using_ast]}]}) do
Macro.expand_once(using_ast, ElixirScript.Translator.State.get().compiler_opts.env)
end
def translate_body(body, env) do
{ body, env } = Translator.translate(body, env)
body = case body do
[%ESTree.BlockStatement{ body: body }] ->
body
%ESTree.BlockStatement{ body: body } ->
body
_ ->
List.wrap(body)
end
{ body, env }
end
def extract_functions_from_module({:__block__, meta, body_list}) do
{ body_list, functions } = Enum.map_reduce(body_list,
%{exported: HashDict.new(), private: HashDict.new()}, fn
({:def, _, [{:when, _, [{name, _, _} | _guards] }, _] } = function, state) ->
{
nil,
%{ state | exported: HashDict.put(state.exported, name, HashDict.get(state.exported, name, []) ++ [function]) }
}
({:def, _, [{name, _, _}, _]} = function, state) ->
{
nil,
%{ state | exported: HashDict.put(state.exported, name, HashDict.get(state.exported, name, []) ++ [function]) }
}
({:defp, _, [{:when, _, [{name, _, _} | _guards] }, _] } = function, state) ->
{
nil,
%{ state | private: HashDict.put(state.private, name, HashDict.get(state.private, name, []) ++ [function]) }
}
({:defp, _, [{name, _, _}, _]} = function, state) ->
{
nil,
%{ state | private: HashDict.put(state.private, name, HashDict.get(state.private, name, []) ++ [function]) }
}
(x, state) ->
{ x, state }
end)
body_list = Enum.filter(body_list, fn(x) -> !is_nil(x) end)
body = {:__block__, meta, body_list}
{ body, functions }
end
def extract_functions_from_module(body) do
extract_functions_from_module({:__block__, [], List.wrap(body)})
end
def extract_imports_from_body(body) do
Enum.partition(body, fn(x) ->
case x do
%ESTree.ImportDeclaration{} ->
true
_ ->
false
end
end)
end
def extract_structs_from_body(body, env) do
module_js_name = Utils.name_to_js_name(env.module)
Enum.partition(body, fn(x) ->
case x do
%ESTree.VariableDeclaration{declarations: [%ESTree.VariableDeclarator{id: %ESTree.Identifier{name: ^module_js_name} } ] } ->
true
_ ->
false
end
end)
end
defp make_defstruct_property(_, []) do
[]
end
defp make_defstruct_property(module_name, [the_struct]) do
module_js_name = Utils.name_to_js_name(module_name)
case the_struct do
%ESTree.VariableDeclaration{declarations: [%ESTree.VariableDeclarator{id: %ESTree.Identifier{name: ^module_js_name} } ] } ->
[JS.property(JS.identifier(module_js_name), JS.identifier(module_js_name), :init, true)]
end
end
def make_std_lib_import() do
compiler_opts = ElixirScript.Translator.State.get().compiler_opts
case compiler_opts.import_standard_libs do
true ->
[ElixirScript.Translator.Import.create_standard_lib_imports(compiler_opts.core_path)]
false ->
[]
end
end
def process_functions(%{ exported: exported, private: private }, env) do
exported_functions = Enum.map(Dict.keys(exported), fn(key) ->
functions = Dict.get(exported, key)
{ functions, _ } = Function.process_function(key, functions, env)
{ key, functions }
end)
private_functions = Enum.map(Dict.keys(private), fn(key) ->
functions = Dict.get(private, key)
{ functions, _ } = Function.process_function(key, functions, env)
{ key, functions }
end)
{ exported_functions, private_functions }
end
def make_attribute(name, value, env) do
declarator = JS.variable_declarator(
JS.identifier(name),
ElixirScript.Translator.translate!(value, env)
)
JS.variable_declaration([declarator], :const)
end
def make_imports(enum) do
Enum.map(enum, fn(x) -> ElixirScript.Translator.Import.make_import(x) end)
end
end