Skip to content

Commit 579dbcb

Browse files
committed
Add FFI for javascript interop
1 parent 9f4630d commit 579dbcb

File tree

12 files changed

+135
-77
lines changed

12 files changed

+135
-77
lines changed

lib/elixir_script/ffi.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
defmodule ElixirScript.FFI do
2+
defmacro __using__(opts) do
3+
quote do
4+
import ElixirScript.FFI
5+
Module.register_attribute __MODULE__, :__foreign_info__, persist: true
6+
@__foreign_info__ %{path: unquote(Keyword.get(opts, :path, nil))}
7+
end
8+
end
9+
10+
defmacro foreign({name, _, args}) do
11+
quote do
12+
def unquote(name)(unquote_splicing(args)), do: nil
13+
end
14+
end
15+
end

lib/elixir_script/lib/agent.ex

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,47 @@ defmodule ElixirScript.Agent do
22
@moduledoc false
33

44
def start(fun, options \\ []) do
5-
pid = JS.new JS.Bootstrap.Core.PID, []
6-
75
name = if Keyword.has_key?(options, :name) do
86
Keyword.get(options, :name)
97
else
108
nil
119
end
1210

13-
ElixirScript.Store.create(pid, fun.(), name)
11+
pid = Bootstrap.Core.Store.create(fun.(), name)
1412
{ :ok, pid }
1513
end
1614

1715
def start_link(fun, options \\ []) do
18-
pid = JS.new JS.Bootstrap.Core.PID, []
19-
2016
name = if Keyword.has_key?(options, :name) do
2117
Keyword.get(options, :name)
2218
else
2319
nil
2420
end
2521

26-
ElixirScript.Store.create(pid, fun.(), name)
22+
pid = Bootstrap.Core.Store.create(fun.(), name)
2723
{ :ok, pid }
2824
end
2925

3026
def stop(agent) do
31-
ElixirScript.Store.remove(agent)
27+
Bootstrap.Core.Store.remove(agent)
3228
:ok
3329
end
3430

3531
def update(agent, fun) do
36-
current_state = ElixirScript.Store.read(agent)
37-
ElixirScript.Store.update(agent, fun.(current_state))
32+
current_state = Bootstrap.Core.Store.read(agent)
33+
Bootstrap.Core.Store.update(agent, fun.(current_state))
3834
:ok
3935
end
4036

4137
def get(agent, fun) do
42-
current_state = ElixirScript.Store.read(agent)
38+
current_state = Bootstrap.Core.Store.read(agent)
4339
fun.(current_state)
4440
end
4541

4642
def get_and_update(agent, fun) do
47-
current_state = ElixirScript.Store.read(agent)
43+
current_state = Bootstrap.Core.Store.read(agent)
4844
{val, new_state} = fun.(current_state)
49-
ElixirScript.Store.update(agent, new_state)
45+
Bootstrap.Core.Store.update(agent, new_state)
5046
val
5147
end
5248

lib/elixir_script/lib/store.ex

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,12 @@
1-
defmodule ElixirScript.Store do
1+
defmodule Bootstrap.Core.Store do
2+
@moduledoc false
3+
use ElixirScript.FFI
24

3-
defp get_key(key) do
4-
real_key = case JS.__elixirscript_names__.has(key) do
5-
true ->
6-
JS.__elixirscript_names__.get(key)
7-
false ->
8-
key
9-
end
5+
foreign create(value, name \\ nil)
106

11-
case JS.__elixirscript_store__.has(real_key) do
12-
true ->
13-
real_key
14-
false ->
15-
JS.throw JS.new(JS.Error, ["Key Not Found"])
16-
end
17-
end
18-
19-
def create(key, value, name \\ nil) do
20-
if name != nil do
21-
JS.__elixirscript_names__.set(name, key)
22-
end
23-
24-
JS.__elixirscript_store__.set(key, value)
25-
end
26-
27-
def update(key, value) do
28-
real_key = get_key(key)
29-
JS.__elixirscript_store__.set(real_key, value)
30-
end
31-
32-
def read(key) do
33-
real_key = get_key(key)
34-
JS.__elixirscript_store__.get(real_key)
35-
end
36-
37-
def remove(key) do
38-
real_key = get_key(key)
39-
JS.__elixirscript_store__.delete(real_key)
40-
end
7+
foreign update(key, value)
418

9+
foreign read(key)
4210

11+
foreign remove(key)
4312
end

lib/elixir_script/passes/output.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ defmodule ElixirScript.Output do
1919

2020
opts = ModuleState.get_compiler_opts(pid)
2121

22-
bundle(modules, opts)
22+
bundle(modules, opts, ModuleState.js_modules(pid))
2323
|> output(Map.get(opts, :output))
2424
end
2525

26-
defp bundle(modules, opts) do
26+
defp bundle(modules, opts, js_modules) do
2727
modules
28-
|> ElixirScript.Output.JSModule.compile(opts)
28+
|> ElixirScript.Output.JSModule.compile(opts, js_modules)
2929
|> List.wrap
3030
|> Builder.program
3131
|> prepare_js_ast

lib/elixir_script/passes/output/js_module.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@ defmodule ElixirScript.Output.JSModule do
33

44
alias ESTree.Tools.Builder, as: J
55

6-
def compile(body, opts) do
6+
def compile(body, opts, js_modules) do
77
declarator = J.variable_declarator(
88
J.identifier("Elixir"),
99
J.object_expression([])
1010
)
1111

1212
elixir = J.variable_declaration([declarator], :const)
1313

14-
table_additions = Enum.map(opts.js_modules, fn
14+
table_additions = Enum.map(js_modules, fn
1515
{module, _} -> add_import_to_table(module)
1616
{module, _, _} -> add_import_to_table(module)
1717
end)
1818

1919
ast = opts.module_formatter.build(
2020
[],
21-
opts.js_modules,
21+
js_modules,
2222
[elixir, create_atom_table(), start(), load()] ++ table_additions ++ body,
2323
J.identifier("Elixir")
2424
)

lib/elixir_script/passes/translate/forms/remote.ex

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -167,20 +167,7 @@ defmodule ElixirScript.Translate.Forms.Remote do
167167
end
168168

169169
defp process_js_module_name(module, _) do
170-
case Module.split(module) do
171-
["JS"] ->
172-
J.member_expression(
173-
J.member_expression(
174-
J.identifier("Bootstrap"),
175-
J.identifier("Core")
176-
),
177-
J.identifier("global")
178-
)
179-
["JS" | rest] ->
180-
Identifier.make_namespace_members(rest)
181-
x ->
182-
Identifier.make_namespace_members(x)
183-
end
170+
Identifier.make_namespace_members(module)
184171
end
185172

186173
defp erlang_compat_function(module, function) do

lib/elixir_script/passes/translate/module.ex

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ defmodule ElixirScript.Translate.Module do
1212
ElixirScript.Translate.Protocol.compile(module, info, pid)
1313
end
1414

15+
def compile(module, %{attributes: [__foreign_info__: %{path: path}]}, pid) do
16+
ModuleState.put_javascript_module(pid, module, path)
17+
18+
nil
19+
end
20+
1521
def compile(module, info, pid) do
1622
%{
1723
attributes: attrs,
@@ -133,12 +139,10 @@ defmodule ElixirScript.Translate.Module do
133139
"""
134140
def is_js_module(module, state) do
135141
cond do
136-
module in ModuleState.get_javascript_modules(state.pid) ->
142+
module in ModuleState.list_javascript_modules(state.pid) ->
137143
true
138144
module === Elixir ->
139145
false
140-
is_elixir_module(module) and hd(Module.split(module)) == "JS" ->
141-
true
142146
true ->
143147
false
144148
end

lib/elixir_script/state.ex

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ defmodule ElixirScript.State do
88
%{
99
compiler_opts: compiler_opts,
1010
modules: Keyword.new,
11-
refs: []
11+
refs: [],
12+
js_modules: []
1213
}
1314
end)
1415
end
@@ -57,9 +58,18 @@ defmodule ElixirScript.State do
5758
end)
5859
end
5960

60-
def get_javascript_modules(pid) do
61+
def put_javascript_module(pid, module, path) do
62+
Agent.update(pid, fn(state) ->
63+
js_modules = Map.get(state, :js_modules, [])
64+
js_modules = js_modules ++ [{module, path}]
65+
66+
%{ state | js_modules: js_modules }
67+
end)
68+
end
69+
70+
def list_javascript_modules(pid) do
6171
Agent.get(pid, fn(state) ->
62-
Map.get(state.compiler_opts, :js_modules, [])
72+
state.js_modules
6373
|> Enum.map(fn
6474
{module_name, _path} ->
6575
module_name
@@ -69,6 +79,26 @@ defmodule ElixirScript.State do
6979
end)
7080
end
7181

82+
def js_modules(pid) do
83+
Agent.get(pid, fn(state) ->
84+
state.js_modules
85+
|> Enum.filter(fn
86+
{_, nil} -> false
87+
_ -> true
88+
end)
89+
end)
90+
end
91+
92+
def list_foreign_modules(pid) do
93+
Agent.get(pid, fn(state) ->
94+
state.modules
95+
|> Enum.filter(fn
96+
(%{attributes: [__foreign_info__: _]}) -> true
97+
(_) -> false
98+
end)
99+
end)
100+
end
101+
72102
def list_modules(pid) do
73103
Agent.get(pid, fn(state) ->
74104
state.modules

src/javascript/lib/core.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import SpecialForms from './core/special_forms';
55
import erlang from './core/erlang_compat/erlang';
66
import maps from './core/erlang_compat/maps';
77
import lists from './core/erlang_compat/lists';
8+
import Store from './core/store';
89

910
class Integer {}
1011
class Float {}
@@ -36,8 +37,9 @@ export default {
3637
Float,
3738
Functions,
3839
SpecialForms,
40+
Store,
3941
global: globalState,
4042
erlang,
4143
maps,
42-
lists,
44+
lists
4345
};

src/javascript/lib/core/store.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import Core from '../core';
2+
3+
function get_key(key) {
4+
let real_key = key;
5+
6+
if (__elixirscript_names__.has(key)) {
7+
real_key = __elixirscript_names__.get(key);
8+
}
9+
10+
if (__elixirscript_store__.has(real_key)) {
11+
return real_key;
12+
}
13+
14+
throw new Error(`Key ${real_key} not found`);
15+
}
16+
17+
function create(value, name = null) {
18+
const key = new Core.PID();
19+
20+
if (name !== null) {
21+
__elixirscript_names__.set(name, key);
22+
}
23+
24+
__elixirscript_store__.set(key, value);
25+
}
26+
27+
function update(key, value) {
28+
const real_key = get_key(key);
29+
__elixirscript_store__.set(real_key, value);
30+
}
31+
32+
function read(key) {
33+
const real_key = get_key(key);
34+
__elixirscript_store__.get(real_key);
35+
}
36+
37+
function remove(key) {
38+
const real_key = get_key(key);
39+
__elixirscript_store__.delete(real_key);
40+
}
41+
42+
export default {
43+
create,
44+
update,
45+
read,
46+
remove
47+
};

0 commit comments

Comments
 (0)