|
1 | 1 | defmodule ElixirScript do |
2 | | - require Logger |
3 | | - |
4 | | - @moduledoc """ |
5 | | - Translates Elixir into JavaScript. |
6 | | -
|
7 | | - All compile functions return a list of |
8 | | - transpiled javascript code or a tuple consisting of |
9 | | - the file name for the code and the transpiled javascript code. |
10 | | -
|
11 | | - All compile functions also take an optional opts parameter |
12 | | - that controls transpiler output. |
13 | | -
|
14 | | - Available options are: |
15 | | - * `:include_path` - a boolean controlling whether to return just the JavaScript code |
16 | | - or a tuple of the file name and the JavaScript code |
17 | | - * `:core_path` - The es6 import path used to import the elixirscript core. |
18 | | - When using this option, the Elixir.js file is not exported |
19 | | - * `:full_build` - For compile_path, tells the compiler to perform a full build instead of incremental one |
20 | | - * `:output` - option to tell compiler how to output data |
21 | | - * `nil`: Return as list |
22 | | - * `:stdout`: Write to standard out |
23 | | - * `path (string)`: Write to specified path |
24 | | - """ |
25 | | - |
26 | | - defmacro __using__(_) do |
27 | | - quote do |
28 | | - import Kernel, except: [ |
29 | | - if: 2, unless: 2, abs: 1, apply: 2, apply: 3, binary_part: 3, hd: 1, |
30 | | - tl: 1, is_atom: 1, is_binary: 1, is_bitstring: 1, is_boolean: 1, is_float: 1, |
31 | | - is_function: 1, is_function: 2, is_integer: 1, is_list: 1, is_number: 1, |
32 | | - is_pid: 1, is_tuple: 1, is_map: 1, is_port: 1, is_reference: 1, length: 1, |
33 | | - map_size: 1, max: 2, min: 2, round: 1, trunc: 1, tuple_size: 1, elem: 2, is_nil: 1, |
34 | | - make_ref: 1, spawn: 1, spawn: 3, spawn_link: 1, spawn_link: 3, spawn_monitor: 1, |
35 | | - spawn_monitor: 3, send: 2, self: 0, match?: 2, to_string: 1, "|>": 2, in: 2, "..": 2, |
36 | | - sigil_r: 2 |
37 | | - ] |
38 | | - import ElixirScript.Kernel |
39 | | - require JS |
40 | | - end |
41 | | - end |
42 | | - |
43 | | - # This is the serialized state of the ElixirScript.State module containing references to the standard library |
44 | | - @lib_path Application.get_env(:elixir_script, :lib_path) |
45 | | - @version Mix.Project.config[:version] |
46 | | - |
47 | | - @doc """ |
48 | | - Compiles the given Elixir code string |
49 | | - """ |
50 | | - @spec compile(binary, Map.t) :: [binary | {binary, binary} | :ok] |
51 | | - def compile(elixir_code, opts \\ %{}) do |
52 | | - elixir_code |
53 | | - |> List.wrap |
54 | | - |> Enum.map(fn(x) -> |
55 | | - x |
56 | | - |> Code.string_to_quoted! |
57 | | - |> compile_quoted(opts) |
58 | | - end) |
59 | | - |> List.flatten |
60 | | - end |
61 | | - |
62 | | - @doc """ |
63 | | - Compiles the given Elixir code in quoted form |
64 | | - """ |
65 | | - @spec compile_quoted(Macro.t, Map.t) :: [binary | {binary, binary} | :ok] |
66 | | - def compile_quoted(quoted, opts \\ %{}) do |
67 | | - |
68 | | - opts = build_compiler_options(opts) |
69 | | - |
70 | | - data = quoted |
71 | | - |> get_modules_from_quoted |
72 | | - |> Enum.map(fn(x) -> %{ast: x, app: :app} end) |
73 | | - |
74 | | - std_lib_quoted = get_quoted_std_lib() |
75 | | - |
76 | | - %{data: std_lib_quoted ++ data} |
77 | | - |> ElixirScript.Passes.Init.execute(opts) |
78 | | - |> shared_passes(opts) |
79 | | - end |
80 | | - |
81 | | - defp shared_passes(compiler_data, opts) do |
82 | | - compiler_data |
83 | | - |> ElixirScript.Passes.FindModules.execute(opts) |
84 | | - |> ElixirScript.Passes.FindLoadOnly.execute(opts) |
85 | | - |> ElixirScript.Passes.HandleOverridables.execute(opts) |
86 | | - |> ElixirScript.Passes.FindFunctions.execute(opts) |
87 | | - |> ElixirScript.Passes.JavaScriptAST.execute(opts) |
88 | | - |> ElixirScript.Passes.ConsolidateProtocols.execute(opts) |
89 | | - |> ElixirScript.Passes.RemoveUnused.execute(opts) |
90 | | - |> ElixirScript.Passes.CreateJSModules.execute(opts) |
91 | | - |> ElixirScript.Passes.JavaScriptCode.execute(opts) |
92 | | - |> ElixirScript.Passes.HandleOutput.execute(opts) |
93 | | - end |
94 | | - |
95 | | - defp get_quoted_std_lib() do |
96 | | - files = [get_std_lib_path(), "**", "*.ex"] |
97 | | - |> Path.join |
98 | | - |> Path.wildcard |
99 | | - |
100 | | - files |
101 | | - |> Enum.map(fn path -> File.read!(path) end) |
102 | | - |> Enum.map(&Code.string_to_quoted!(&1)) |
103 | | - |> Enum.flat_map(&get_modules_from_quoted(&1)) |
104 | | - |> Enum.map(fn(x) -> %{ast: x, app: :elixir} end) |
105 | | - end |
106 | | - |
107 | | - defp get_modules_from_quoted(quoted) do |
108 | | - results = case quoted do |
109 | | - {:__block__, _, list} -> |
110 | | - {modules, not_modules} = Enum.partition(list, |
111 | | - fn |
112 | | - {type, _, _ } when type in [:defprotocol, :defimpl, :defmodule] -> |
113 | | - true |
114 | | - _ -> |
115 | | - false |
116 | | - end) |
117 | | - |
118 | | - temp_module = case not_modules do |
119 | | - [] -> |
120 | | - [] |
121 | | - _ -> |
122 | | - [{:defmodule, [], [{:__aliases__, [], [:ElixirScript, :Temp]}, [do: { :__block__, [], not_modules }]]}] |
123 | | - end |
124 | | - |
125 | | - modules ++ temp_module |
126 | | - |
127 | | - {type, _, _ } = x when type in [:defprotocol, :defimpl, :defmodule] -> |
128 | | - x |
129 | | - x -> |
130 | | - {:defmodule, [], [{:__aliases__, [], [:ElixirScript, :Temp]}, [do: { :__block__, [], [x] }]]} |
131 | | - end |
132 | | - |
133 | | - List.wrap(results) |
134 | | - end |
135 | | - |
136 | | - @doc """ |
137 | | - Compiles the elixir files found at the given path |
138 | | - """ |
139 | | - @spec compile_path(binary | [binary] | map, Map.t) :: [binary | {binary, binary} | :ok] |
140 | | - def compile_path(path, opts \\ %{}) |
141 | | - |
142 | | - def compile_path(path, opts) when is_binary(path) do |
143 | | - compile_path([path], opts) |
144 | | - end |
145 | | - |
146 | | - def compile_path(path, opts) when is_list(path) do |
147 | | - built_opts = build_compiler_options(opts) |
148 | | - |
149 | | - app_name = cond do |
150 | | - !is_nil(built_opts[:app]) -> |
151 | | - built_opts[:app] |
152 | | - Code.ensure_loaded?(Mix) -> |
153 | | - Mix.Project.config()[:app] |
154 | | - true -> |
155 | | - :app |
156 | | - end |
157 | | - |
158 | | - compile_path(Map.put(%{}, app_name, path), opts) |
159 | | - end |
160 | | - |
161 | | - def compile_path(path, opts) do |
162 | | - opts = build_compiler_options(opts) |
163 | | - |
164 | | - deps = path |
165 | | - |> Map.to_list |
166 | | - |> Enum.map(fn {app, path} -> {app, List.wrap(path)} end) |
167 | | - |
168 | | - deps_wrapped = [{:elixir, List.wrap(get_std_lib_path())}] ++ deps |
169 | | - |
170 | | - result = %{data: deps_wrapped} |
171 | | - |> ElixirScript.Passes.Init.execute(opts) |
172 | | - |> ElixirScript.Passes.ASTFromFile.execute(opts) |
173 | | - |> ElixirScript.Passes.LoadModules.execute(opts) |
174 | | - |> shared_passes(opts) |
175 | | - |
176 | | - result |
177 | | - end |
178 | | - |
179 | | - defp build_compiler_options(opts) do |
180 | | - default_options = Map.new |
181 | | - |> Map.put(:include_path, false) |
182 | | - |> Map.put(:root, nil) |
183 | | - |> Map.put(:env, __ENV__) |
184 | | - |> Map.put(:import_standard_libs, true) |
185 | | - |> Map.put(:core_path, "Elixir.Bootstrap") |
186 | | - |> Map.put(:full_build, false) |
187 | | - |> Map.put(:output, nil) |
188 | | - |> Map.put(:app, :app) |
189 | | - |> Map.put(:format, :es) |
190 | | - |> Map.put(:js_modules, []) |
191 | | - |> Map.put(:remove_unused, false) |
192 | | - |
193 | | - options = Map.merge(default_options, opts) |
194 | | - Map.put(options, :module_formatter, get_module_formatter(options[:format])) |
195 | | - end |
196 | | - |
197 | | - defp get_module_formatter(:umd) do |
198 | | - ElixirScript.ModuleSystems.UMD |
199 | | - end |
200 | | - |
201 | | - defp get_module_formatter(:common) do |
202 | | - ElixirScript.ModuleSystems.Common |
203 | | - end |
204 | | - |
205 | | - defp get_module_formatter(_) do |
206 | | - ElixirScript.ModuleSystems.ES |
207 | | - end |
208 | | - |
209 | | - @doc """ |
210 | | - Returns the contents of the bootrstrap js file |
211 | | - """ |
212 | | - @spec get_bootstrap_js(string) :: string |
213 | | - def get_bootstrap_js(module_format) do |
214 | | - path = Path.join([operating_path, "build", to_string(module_format), "Elixir.Bootstrap.js"]) |
215 | | - File.read!(path) |
216 | | - end |
217 | | - |
218 | | - #Gets path to js files whether the mix project is available |
219 | | - #or when used as an escript |
220 | | - defp operating_path() do |
221 | | - case @lib_path do |
222 | | - nil -> |
223 | | - if Code.ensure_loaded?(Mix.Project) do |
224 | | - Path.join([Mix.Project.build_path, "lib", "elixir_script", "priv"]) |
225 | | - else |
226 | | - split_path = Path.split(Application.app_dir(:elixirscript)) |
227 | | - replaced_path = List.delete_at(split_path, length(split_path) - 1) |
228 | | - replaced_path = List.delete_at(replaced_path, length(replaced_path) - 1) |
229 | | - Path.join(replaced_path) |
230 | | - end |
231 | | - lib_path -> |
232 | | - lib_path |
233 | | - end |
234 | | - end |
235 | | - |
236 | | - defp get_std_lib_path() do |
237 | | - Path.join([operating_path(), "std_lib"]) |
238 | | - end |
239 | | - |
240 | 2 | end |
0 commit comments