Skip to content

Commit 6246f67

Browse files
committed
Merge branch 'master' into async_switch
2 parents 499b891 + 66ab1e1 commit 6246f67

File tree

21 files changed

+408
-120
lines changed

21 files changed

+408
-120
lines changed

CHANGELOG.md

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,35 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
66

77
## [unreleased]
88
### Added
9-
- Reimplement `String.split_at/2` to make sure Unicode library isn't compiled
10-
- Added `ElixirScript.JS.map_to_object/2` with options [keys: :string, symbols: false]
11-
- Added `ElixirScript.JS.object_to_map/1|2` with options [keys: :atom, recurse_array: true]
9+
- [Compiler will now accept a path to Elixir Files to compile](https://github.com/elixirscript/elixirscript/issues/420)
10+
- [Added `ElixirScript.JS.map_to_object/2` with options [keys: :string, symbols: false]](https://github.com/elixirscript/elixirscript/issues/362)
11+
- [Added `ElixirScript.JS.object_to_map/1|2` with options [keys: :atom, recurse_array: true]](https://github.com/elixirscript/elixirscript/issues/381)
12+
- [Fully implement `__info__` on modules](https://github.com/elixirscript/elixirscript/pull/378)
13+
- [Concurrent Compilation](https://github.com/elixirscript/elixirscript/issues/376)
14+
- [The following erlang functions have been implemented](https://github.com/elixirscript/elixirscript/issues/306):
15+
* :erlang.nodes/0
16+
* :erlang.nodes/1
17+
* :math.log2/1
18+
* :binary.copy/1
19+
* :binary.copy/2
20+
* :binary.part/2
21+
* :binary.part/3
22+
* :binary.replace/3
23+
* :binary.replace/4 (some options still missing)
1224

1325
### Fixed
1426
- Make sure not to add underscores to erlang functions
15-
- Make sure any variable names that are javascript keywords are handled properly
27+
- [Make sure any variable names that are javascript keywords are handled properly](https://github.com/elixirscript/elixirscript/issues/355)
28+
- [Make sure variables that begin with `_` are available](https://github.com/elixirscript/elixirscript/issues/356)
29+
- [Finding the use of functions within anonymous functions](https://github.com/elixirscript/elixirscript/issues/358)
30+
- [Reimplement `String.split_at/2` to make sure Unicode library isn't compiled](https://github.com/elixirscript/elixirscript/issues/353)
31+
- [byte_size does not work on binaries that started via "" elixir string syntax](https://github.com/elixirscript/elixirscript/issues/384)
32+
- [using . (dot) reference syntax on a map fails when value is a function](https://github.com/elixirscript/elixirscript/issues/380)
33+
- [Make sure that remote ast works correctly with variables](https://github.com/elixirscript/elixirscript/issues/390)
34+
- [Make sure == works as expected](https://github.com/elixirscript/elixirscript/issues/382)
35+
- [Make sure that erlang function names that are also JavaScript keywords are not filters](https://github.com/elixirscript/elixirscript/issues/359)
36+
- [erlang.error now throws errors resembling those in Elixir](https://github.com/elixirscript/elixirscript/pull/397)
37+
- [Map.get fails if key is tuple or list](https://github.com/elixirscript/elixirscript/issues/406)
1638

1739
## [0.30.0] - 2017-08-15
1840

lib/elixir_script/beam.ex

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule ElixirScript.Beam do
77
For protocols, this will return a list of
88
all the protocol implementations
99
"""
10-
@spec debug_info(atom) :: {:ok | :error, map | binary}
10+
@spec debug_info(atom | bitstring) :: {:ok | :error, map | binary}
1111
def debug_info(module)
1212

1313
# We get debug info from String and then replace
@@ -39,9 +39,23 @@ defmodule ElixirScript.Beam do
3939
do_debug_info(module)
4040
end
4141

42-
defp do_debug_info(module) when is_atom(module) do
43-
with {_, beam, beam_path} <- :code.get_object_code(module),
44-
{:ok, {^module, [debug_info: {:debug_info_v1, backend, data}]}} <- :beam_lib.chunks(beam, [:debug_info]),
42+
def debug_info(beam) when is_bitstring(beam) do
43+
do_debug_info(beam)
44+
end
45+
46+
defp do_debug_info(module, path \\ nil)
47+
48+
defp do_debug_info(module, _) when is_atom(module) do
49+
case :code.get_object_code(module) do
50+
{_, beam, beam_path} ->
51+
do_debug_info(beam, beam_path)
52+
:error ->
53+
{:error, "Unknown module"}
54+
end
55+
end
56+
57+
defp do_debug_info(beam, beam_path) do
58+
with {:ok, {module, [debug_info: {:debug_info_v1, backend, data}]}} <- :beam_lib.chunks(beam, [:debug_info]),
4559
{:ok, {^module, attribute_info}} = :beam_lib.chunks(beam, [:attributes]) do
4660

4761
if Keyword.get(attribute_info[:attributes], :protocol) do
@@ -62,6 +76,11 @@ defmodule ElixirScript.Beam do
6276
end
6377
end
6478

79+
defp process_debug_info({:ok, info}, nil) do
80+
info = Map.put(info, :last_modified, nil)
81+
{:ok, info}
82+
end
83+
6584
defp process_debug_info({:ok, info}, beam_path) do
6685
info = case File.stat(beam_path, time: :posix) do
6786
{:ok, file_info} ->

lib/elixir_script/cli.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ defmodule ElixirScript.CLI do
3636

3737
defp help_message do
3838
"""
39-
usage: elixirscript <module> [options]
39+
usage: elixirscript <module | path> [options]
4040
<module> the entry module of your application
41+
<path> the path to .ex(s) files to compile
4142
4243
options:
4344
-o --output [path] places output at the given path.

lib/elixir_script/compiler.ex

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ defmodule ElixirScript.Compiler do
22
@moduledoc """
33
The entry point for the ElixirScript compilation process.
44
Takes the given module(s) and compiles them and all modules
5-
and functions they use into JavaScript
5+
and functions they use into JavaScript.
6+
7+
Will also take a path to Elixir files
68
"""
79

810
@doc """
9-
Takes either a module name or a list of module names as
11+
Takes either a module name, list of module names, or a path as
1012
the entry point(s) of an application/library. From there
1113
it will determine which modules and functions are needed
1214
to be compiled.
@@ -22,36 +24,78 @@ defmodule ElixirScript.Compiler do
2224
2325
* `root`: Optional root for imports of FFI JavaScript modules. Defaults to `.`.
2426
"""
25-
@spec compile(atom | [atom], []) :: nil
26-
def compile(entry_modules, opts \\ []) do
27-
opts = build_compiler_options(opts, entry_modules)
28-
{:ok, pid} = ElixirScript.State.start_link()
27+
alias ElixirScript.{
28+
State,
29+
Translate,
30+
FindUsedModules,
31+
FindUsedFunctions,
32+
Output
33+
}
34+
alias ElixirScript.ModuleSystems.ES
35+
alias Kernel.ParallelCompiler
36+
37+
@spec compile(atom | [atom] | binary, []) :: nil
38+
def compile(path, opts \\ [])
39+
40+
def compile(path, opts) when is_binary(path) do
41+
opts = build_compiler_options(opts)
42+
{:ok, pid} = State.start_link()
43+
44+
path = if String.ends_with?(path, [".ex", ".exs"]) do
45+
path
46+
else
47+
Path.join([path, "**", "*.{ex,exs}"])
48+
end
49+
50+
files = Path.wildcard(path)
51+
52+
ParallelCompiler.files(files, [
53+
each_module: &on_module_compile(pid, &1, &2, &3)
54+
])
55+
56+
entry_modules = pid
57+
|> State.get_in_memory_modules
58+
|> Keyword.keys
59+
60+
do_compile(entry_modules, pid, opts)
61+
end
62+
63+
def compile(entry_modules, opts) do
64+
opts = build_compiler_options(opts)
65+
{:ok, pid} = State.start_link()
2966

3067
entry_modules = List.wrap(entry_modules)
3168

32-
ElixirScript.FindUsedModules.execute(entry_modules, pid)
69+
do_compile(entry_modules, pid, opts)
70+
end
3371

34-
ElixirScript.FindUsedFunctions.execute(entry_modules, pid)
72+
defp do_compile(entry_modules, pid, opts) do
73+
FindUsedModules.execute(entry_modules, pid)
3574

36-
modules = ElixirScript.State.list_modules(pid)
37-
ElixirScript.Translate.execute(modules, pid)
75+
FindUsedFunctions.execute(entry_modules, pid)
3876

39-
modules = ElixirScript.State.list_modules(pid)
40-
result = ElixirScript.Output.execute(modules, pid, opts)
77+
modules = State.list_modules(pid)
78+
Translate.execute(modules, pid)
4179

42-
ElixirScript.State.stop(pid)
80+
modules = State.list_modules(pid)
81+
result = Output.execute(modules, pid, opts)
82+
83+
State.stop(pid)
4384

4485
result
4586
end
4687

47-
defp build_compiler_options(opts, entry_modules) do
88+
defp build_compiler_options(opts) do
4889
default_options = Map.new
4990
|> Map.put(:output, Keyword.get(opts, :output))
5091
|> Map.put(:format, :es)
51-
|> Map.put(:entry_modules, entry_modules)
5292
|> Map.put(:root, Keyword.get(opts, :root, "."))
5393

5494
options = default_options
55-
Map.put(options, :module_formatter, ElixirScript.ModuleSystems.ES)
95+
Map.put(options, :module_formatter, ES)
96+
end
97+
98+
defp on_module_compile(pid, _file, module, beam) do
99+
State.put_in_memory_module(pid, module, beam)
56100
end
57101
end

lib/elixir_script/passes/find_used_functions.ex

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,19 @@ defmodule ElixirScript.FindUsedFunctions do
1111
def execute(entry_modules, pid) do
1212
entry_modules
1313
|> List.wrap
14-
|> Task.async_stream(fn
14+
|> Enum.each(fn
1515
module ->
1616
walk_module(module, pid)
1717
end)
18-
|> Stream.run()
1918

2019
pid
2120
|> ElixirScript.State.list_modules
22-
|> Task.async_stream(fn
21+
|> Enum.each(fn
2322
{module, info} ->
2423
if get_in(info, [:attributes, :protocol_impl]) do
2524
walk_module(module, pid)
2625
end
2726
end)
28-
|> Stream.run()
2927
end
3028

3129
defp walk_module(module, pid) do
@@ -139,7 +137,7 @@ defmodule ElixirScript.FindUsedFunctions do
139137
walk(params, state)
140138
end
141139

142-
defp walk({:for, _, generators}, state) do
140+
defp walk({:for, _, generators}, state) when is_list(generators) do
143141
Enum.each(generators, fn
144142
{:<<>>, _, body} ->
145143
walk(body, state)

lib/elixir_script/passes/find_used_modules.ex

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@ defmodule ElixirScript.FindUsedModules do
1010
def execute(modules, pid) do
1111
modules
1212
|> List.wrap
13-
|> Task.async_stream(fn(module) ->
14-
if ElixirScript.State.get_module(pid, module) == nil do
15-
do_execute(module, pid)
16-
end
13+
|> Enum.each(fn(module) ->
14+
do_execute(module, pid)
1715
end)
18-
|> Stream.run()
1916
end
2017

2118
defp do_execute(module, pid) do
22-
case ElixirScript.Beam.debug_info(module) do
19+
result = case ModuleState.get_in_memory_module(pid, module) do
20+
nil ->
21+
ElixirScript.Beam.debug_info(module)
22+
beam ->
23+
ElixirScript.Beam.debug_info(beam)
24+
end
25+
26+
case result do
2327
{:ok, info} ->
2428
walk_module(module, info, pid)
2529
{:ok, module, implementations} ->
@@ -74,7 +78,9 @@ defmodule ElixirScript.FindUsedModules do
7478
module: module
7579
}
7680

77-
Enum.each(reachable_defs, &walk(&1, state))
81+
Enum.each(reachable_defs, fn(x) ->
82+
walk(x, state)
83+
end)
7884
end
7985

8086
defp walk_protocol(module, implementations, pid) do
@@ -166,7 +172,7 @@ defmodule ElixirScript.FindUsedModules do
166172
walk(params, state)
167173
end
168174

169-
defp walk({:for, _, generators}, state) do
175+
defp walk({:for, _, generators}, state) when is_list(generators) do
170176
walk(Collectable, state)
171177

172178
Enum.each(generators, fn

lib/elixir_script/passes/output/js_module.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule ElixirScript.Output.JSModule do
1010
imports = opts.module_formatter.build_imports(js_modules)
1111
exports = opts.module_formatter.build_export(J.identifier("Elixir"))
1212

13-
[elixir, create_atom_table(), start(), load(), imports, body, exports]
13+
[elixir, create_atom_table(), start(), load(), imports] ++ body ++ [exports]
1414
end
1515

1616
def start do

lib/elixir_script/passes/translate/form.ex

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,49 +9,25 @@ defmodule ElixirScript.Translate.Form do
99
alias ElixirScript.Translate.Clause
1010
require Logger
1111

12-
@js_reserved_words [
13-
:break,
14-
:case,
15-
:class,
16-
:const,
17-
:continue,
18-
:debugger,
19-
:default,
20-
:delete,
21-
:do,
22-
:else,
23-
:export,
24-
:extends,
25-
:finally,
26-
:function,
27-
:if,
28-
:import,
29-
:in,
30-
:instanceof,
31-
:new,
32-
:return,
33-
:super,
34-
:switch,
35-
:throw,
36-
:try,
37-
:typeof,
38-
:var,
39-
:void,
40-
:while,
41-
:with,
42-
:yield
43-
]
44-
12+
@spec compile!(any, map) :: ESTree.Node.t
4513
def compile!(ast, state) do
4614
{js_ast, _} = compile(ast, state)
4715

4816
js_ast
4917
end
5018

19+
@spec compile(any, map) :: {ESTree.Node.t, map}
20+
def compile(ast, state)
21+
5122
def compile(nil, state) do
5223
{ J.identifier("null"), state }
5324
end
5425

26+
def compile(map, state) when is_map(map) do
27+
quoted = Code.string_to_quoted!("#{inspect map}")
28+
compile(quoted, state)
29+
end
30+
5531
def compile(form, state) when is_boolean(form) or is_integer(form) or is_float(form) or is_binary(form) do
5632
{ J.literal(form), state }
5733
end
@@ -155,7 +131,7 @@ defmodule ElixirScript.Translate.Form do
155131
{ ast, state }
156132
end
157133

158-
def compile({:for, _, _} = ast, state) do
134+
def compile({:for, _, generators} = ast, state) when is_list(generators) do
159135
For.compile(ast, state)
160136
end
161137

@@ -406,18 +382,11 @@ defmodule ElixirScript.Translate.Form do
406382
end
407383
end
408384

409-
def compile({var, meta, _}, state) when var in @js_reserved_words do
410-
counter = Pattern.get_counter(meta)
411-
412-
var = String.to_atom("__#{var}__")
413-
var = Pattern.get_variable_name(to_string(var) <> counter, state)
414-
{ ElixirScript.Translate.Identifier.make_identifier(var), state }
415-
end
416-
417385
def compile({var, meta, _}, state) do
418386
counter = Pattern.get_counter(meta)
419387

420-
var = Pattern.get_variable_name(to_string(var) <> counter, state)
388+
var = ElixirScript.Translate.Identifier.filter_name(var)
389+
var = Pattern.get_variable_name(var <> counter, state)
421390
{ ElixirScript.Translate.Identifier.make_identifier(var), state }
422391
end
423392

0 commit comments

Comments
 (0)