Current section
Files
Jump to
Current section
Files
lib/elixir_script.ex
defmodule ElixirScript do
alias ElixirScript.Translator
alias ElixirScript.Translator.JSModule
alias ESTree.Tools.Builder
alias ESTree.Tools.Generator
require Logger
@moduledoc """
Translates Elixir into JavaScript.
All compile functions return a list of
transpiled javascript code or a tuple consisting of
the file name for the code and the transpiled javascript code.
All compile functions also take an optional opts parameter
that controls transpiler output.
Available options are:
* `:include_path` - a boolean controlling whether to return just the JavaScript code
or a tuple of the file name and the JavaScript code
* `:root` - a binary path prepended to the path of the standard lib imports if needed
* `:env` - a Macro.env struct to use. This is most useful when using macros. Make sure that the
given env has the macros required. Defaults to __ENV__.
"""
@doc """
Compiles the given Elixir code string
"""
@spec compile(binary, Dict.t) :: [binary | { binary, binary }]
def compile(elixir_code, opts \\ []) do
elixir_code
|> Code.string_to_quoted!
|> compile_quoted(opts)
end
@doc """
Compiles the given Elixir code in quoted form
"""
@spec compile_quoted(Macro.t, Dict.t) :: [binary | { binary, binary }]
def compile_quoted(quoted, opts \\ []) do
include_path = Dict.get(opts, :include_path, false)
root = Dict.get(opts, :root)
env = Dict.get(opts, :env, custom_env)
import_standard_libs? = Dict.get(opts, :import_standard_libs, true)
ElixirScript.State.start_link(root, env)
build_environment([quoted])
state = ElixirScript.State.get()
create_code(include_path, import_standard_libs?)
end
@doc """
Compiles the elixir files found at the given path
"""
@spec compile_path(binary, Dict.t) :: [binary | { binary, binary }]
def compile_path(path, opts \\ []) do
include_path = Dict.get(opts, :include_path, false)
root = Dict.get(opts, :root)
env = Dict.get(opts, :env, custom_env)
ElixirScript.State.start_link(root, env)
path
|> Path.wildcard
|> Enum.map(fn(x) ->
File.read!(x)
|> Code.string_to_quoted!
end)
|> build_environment
create_code(include_path, true)
end
defp build_environment(code_list) do
code_list
|> ElixirScript.Preprocess.Modules.get_info
end
defp custom_env() do
require Logger
require ElixirScript.Lib.JS, as: JS
__ENV__
end
defp create_code(include_path, import_standard_libs?) do
state = ElixirScript.State.get()
current = self
result = state.modules
|> Enum.map(fn(x) ->
result = ElixirScript.Translator.Module.make_module(x.name, x.body, state.env)
|> Enum.map(fn(x) ->
convert_to_code(x, state.root, include_path, state.env, import_standard_libs?)
end)
spawn_link fn -> (send current, { self, result }) end
end)
|> Enum.map(fn (pid) ->
receive do { ^pid, x } -> x end
end)
|> List.flatten
protocol_result = state.protocols |> Dict.to_list
|> ElixirScript.Translator.Protocol.consolidate(state.env)
|> Enum.map(fn(x) ->
convert_to_code(x, state.root, include_path, state.env, import_standard_libs?)
end)
|> List.flatten
ElixirScript.State.stop
result ++ protocol_result
end
@doc """
Copies the javascript that makes up the ElixirScript standard libs
to the specified location
"""
def copy_standard_libs_to_destination(destination) do
File.cp_r!(operating_path <> "/dist", destination)
end
@doc """
Returns the standard lib js code
"""
def standard_libs() do
File.read!(operating_path <> "/dist/elixir.js")
end
defp convert_to_code(js_ast, root, include_path, env, import_standard_libs \\ true) do
js_ast
|> process_module(root, env, import_standard_libs)
|> javascript_ast_to_code
|> process_include_path(include_path)
end
defp process_module(%JSModule{} = module, root, env, import_standard_libs) do
file_path = create_file_name(module)
standard_libs_import = if import_standard_libs do
ElixirScript.Translator.Import.create_standard_lib_imports(root, env)
else
[]
end
program = standard_libs_import ++ module.body
|> ESTree.Tools.Builder.program
{ file_path, program }
end
defp process_module(module, _root, _, _) do
{ "", module }
end
defp create_file_name(%JSModule{ name: module_list }) do
name = ElixirScript.Translator.Import.make_file_path(module_list)
"#{name}.js"
end
defp process_include_path({ _, _ } = pair, true) do
pair
end
defp process_include_path({ _, code }, false) do
code
end
@doc false
def javascript_ast_to_code({ path, js_ast }) do
js_code = javascript_ast_to_code(js_ast)
{ path, js_code }
end
@doc false
def javascript_ast_to_code(js_ast) do
prepare_js_ast(js_ast)
|> Generator.generate
end
defp prepare_js_ast(js_ast) do
case js_ast do
modules when is_list(modules) ->
Enum.reduce(modules, [], fn(x, list) -> list ++ x.body end)
|> Builder.program
%ElixirScript.Translator.Group{body: body} ->
Builder.program(body)
_ ->
js_ast
end
end
defp operating_path() do
try do
Mix.Project.build_path <> "/lib/elixir_script/priv/javascript"
rescue
UndefinedFunctionError ->
split_path = Path.split(Application.app_dir(:ex2js))
replaced_path = List.delete_at(split_path, length(split_path) - 1)
replaced_path = List.delete_at(replaced_path, length(replaced_path) - 1)
Path.join(replaced_path)
end
end
end