From 579dbcbebcdc88eb815575d77d91a8bd3f116dea Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Mon, 10 Jul 2017 08:38:11 -0500 Subject: [PATCH 01/11] Add FFI for javascript interop --- lib/elixir_script/ffi.ex | 15 ++++++ lib/elixir_script/lib/agent.ex | 20 ++++---- lib/elixir_script/lib/store.ex | 45 +++--------------- lib/elixir_script/passes/output.ex | 6 +-- lib/elixir_script/passes/output/js_module.ex | 6 +-- .../passes/translate/forms/remote.ex | 15 +----- lib/elixir_script/passes/translate/module.ex | 10 ++-- lib/elixir_script/state.ex | 36 ++++++++++++-- src/javascript/lib/core.js | 4 +- src/javascript/lib/core/store.js | 47 +++++++++++++++++++ test/support/json.ex | 6 +++ test/support/main.ex | 2 + 12 files changed, 135 insertions(+), 77 deletions(-) create mode 100644 lib/elixir_script/ffi.ex create mode 100644 src/javascript/lib/core/store.js create mode 100644 test/support/json.ex diff --git a/lib/elixir_script/ffi.ex b/lib/elixir_script/ffi.ex new file mode 100644 index 00000000..ff14b80b --- /dev/null +++ b/lib/elixir_script/ffi.ex @@ -0,0 +1,15 @@ +defmodule ElixirScript.FFI do + defmacro __using__(opts) do + quote do + import ElixirScript.FFI + Module.register_attribute __MODULE__, :__foreign_info__, persist: true + @__foreign_info__ %{path: unquote(Keyword.get(opts, :path, nil))} + end + end + + defmacro foreign({name, _, args}) do + quote do + def unquote(name)(unquote_splicing(args)), do: nil + end + end +end \ No newline at end of file diff --git a/lib/elixir_script/lib/agent.ex b/lib/elixir_script/lib/agent.ex index 8ae7b1eb..cb444941 100644 --- a/lib/elixir_script/lib/agent.ex +++ b/lib/elixir_script/lib/agent.ex @@ -2,51 +2,47 @@ defmodule ElixirScript.Agent do @moduledoc false def start(fun, options \\ []) do - pid = JS.new JS.Bootstrap.Core.PID, [] - name = if Keyword.has_key?(options, :name) do Keyword.get(options, :name) else nil end - ElixirScript.Store.create(pid, fun.(), name) + pid = Bootstrap.Core.Store.create(fun.(), name) { :ok, pid } end def start_link(fun, options \\ []) do - pid = JS.new JS.Bootstrap.Core.PID, [] - name = if Keyword.has_key?(options, :name) do Keyword.get(options, :name) else nil end - ElixirScript.Store.create(pid, fun.(), name) + pid = Bootstrap.Core.Store.create(fun.(), name) { :ok, pid } end def stop(agent) do - ElixirScript.Store.remove(agent) + Bootstrap.Core.Store.remove(agent) :ok end def update(agent, fun) do - current_state = ElixirScript.Store.read(agent) - ElixirScript.Store.update(agent, fun.(current_state)) + current_state = Bootstrap.Core.Store.read(agent) + Bootstrap.Core.Store.update(agent, fun.(current_state)) :ok end def get(agent, fun) do - current_state = ElixirScript.Store.read(agent) + current_state = Bootstrap.Core.Store.read(agent) fun.(current_state) end def get_and_update(agent, fun) do - current_state = ElixirScript.Store.read(agent) + current_state = Bootstrap.Core.Store.read(agent) {val, new_state} = fun.(current_state) - ElixirScript.Store.update(agent, new_state) + Bootstrap.Core.Store.update(agent, new_state) val end diff --git a/lib/elixir_script/lib/store.ex b/lib/elixir_script/lib/store.ex index ad5b777c..dd6b4a7a 100644 --- a/lib/elixir_script/lib/store.ex +++ b/lib/elixir_script/lib/store.ex @@ -1,43 +1,12 @@ -defmodule ElixirScript.Store do +defmodule Bootstrap.Core.Store do + @moduledoc false + use ElixirScript.FFI - defp get_key(key) do - real_key = case JS.__elixirscript_names__.has(key) do - true -> - JS.__elixirscript_names__.get(key) - false -> - key - end + foreign create(value, name \\ nil) - case JS.__elixirscript_store__.has(real_key) do - true -> - real_key - false -> - JS.throw JS.new(JS.Error, ["Key Not Found"]) - end - end - - def create(key, value, name \\ nil) do - if name != nil do - JS.__elixirscript_names__.set(name, key) - end - - JS.__elixirscript_store__.set(key, value) - end - - def update(key, value) do - real_key = get_key(key) - JS.__elixirscript_store__.set(real_key, value) - end - - def read(key) do - real_key = get_key(key) - JS.__elixirscript_store__.get(real_key) - end - - def remove(key) do - real_key = get_key(key) - JS.__elixirscript_store__.delete(real_key) - end + foreign update(key, value) + foreign read(key) + foreign remove(key) end \ No newline at end of file diff --git a/lib/elixir_script/passes/output.ex b/lib/elixir_script/passes/output.ex index 14872ba0..a2d3e2fc 100644 --- a/lib/elixir_script/passes/output.ex +++ b/lib/elixir_script/passes/output.ex @@ -19,13 +19,13 @@ defmodule ElixirScript.Output do opts = ModuleState.get_compiler_opts(pid) - bundle(modules, opts) + bundle(modules, opts, ModuleState.js_modules(pid)) |> output(Map.get(opts, :output)) end - defp bundle(modules, opts) do + defp bundle(modules, opts, js_modules) do modules - |> ElixirScript.Output.JSModule.compile(opts) + |> ElixirScript.Output.JSModule.compile(opts, js_modules) |> List.wrap |> Builder.program |> prepare_js_ast diff --git a/lib/elixir_script/passes/output/js_module.ex b/lib/elixir_script/passes/output/js_module.ex index 75eb960f..79f22a83 100644 --- a/lib/elixir_script/passes/output/js_module.ex +++ b/lib/elixir_script/passes/output/js_module.ex @@ -3,7 +3,7 @@ defmodule ElixirScript.Output.JSModule do alias ESTree.Tools.Builder, as: J - def compile(body, opts) do + def compile(body, opts, js_modules) do declarator = J.variable_declarator( J.identifier("Elixir"), J.object_expression([]) @@ -11,14 +11,14 @@ defmodule ElixirScript.Output.JSModule do elixir = J.variable_declaration([declarator], :const) - table_additions = Enum.map(opts.js_modules, fn + table_additions = Enum.map(js_modules, fn {module, _} -> add_import_to_table(module) {module, _, _} -> add_import_to_table(module) end) ast = opts.module_formatter.build( [], - opts.js_modules, + js_modules, [elixir, create_atom_table(), start(), load()] ++ table_additions ++ body, J.identifier("Elixir") ) diff --git a/lib/elixir_script/passes/translate/forms/remote.ex b/lib/elixir_script/passes/translate/forms/remote.ex index 66740877..93b186cc 100644 --- a/lib/elixir_script/passes/translate/forms/remote.ex +++ b/lib/elixir_script/passes/translate/forms/remote.ex @@ -167,20 +167,7 @@ defmodule ElixirScript.Translate.Forms.Remote do end defp process_js_module_name(module, _) do - case Module.split(module) do - ["JS"] -> - J.member_expression( - J.member_expression( - J.identifier("Bootstrap"), - J.identifier("Core") - ), - J.identifier("global") - ) - ["JS" | rest] -> - Identifier.make_namespace_members(rest) - x -> - Identifier.make_namespace_members(x) - end + Identifier.make_namespace_members(module) end defp erlang_compat_function(module, function) do diff --git a/lib/elixir_script/passes/translate/module.ex b/lib/elixir_script/passes/translate/module.ex index 54ca1c7e..049bfda0 100644 --- a/lib/elixir_script/passes/translate/module.ex +++ b/lib/elixir_script/passes/translate/module.ex @@ -12,6 +12,12 @@ defmodule ElixirScript.Translate.Module do ElixirScript.Translate.Protocol.compile(module, info, pid) end + def compile(module, %{attributes: [__foreign_info__: %{path: path}]}, pid) do + ModuleState.put_javascript_module(pid, module, path) + + nil + end + def compile(module, info, pid) do %{ attributes: attrs, @@ -133,12 +139,10 @@ defmodule ElixirScript.Translate.Module do """ def is_js_module(module, state) do cond do - module in ModuleState.get_javascript_modules(state.pid) -> + module in ModuleState.list_javascript_modules(state.pid) -> true module === Elixir -> false - is_elixir_module(module) and hd(Module.split(module)) == "JS" -> - true true -> false end diff --git a/lib/elixir_script/state.ex b/lib/elixir_script/state.ex index f7e82449..464f43eb 100644 --- a/lib/elixir_script/state.ex +++ b/lib/elixir_script/state.ex @@ -8,7 +8,8 @@ defmodule ElixirScript.State do %{ compiler_opts: compiler_opts, modules: Keyword.new, - refs: [] + refs: [], + js_modules: [] } end) end @@ -57,9 +58,18 @@ defmodule ElixirScript.State do end) end - def get_javascript_modules(pid) do + def put_javascript_module(pid, module, path) do + Agent.update(pid, fn(state) -> + js_modules = Map.get(state, :js_modules, []) + js_modules = js_modules ++ [{module, path}] + + %{ state | js_modules: js_modules } + end) + end + + def list_javascript_modules(pid) do Agent.get(pid, fn(state) -> - Map.get(state.compiler_opts, :js_modules, []) + state.js_modules |> Enum.map(fn {module_name, _path} -> module_name @@ -69,6 +79,26 @@ defmodule ElixirScript.State do end) end + def js_modules(pid) do + Agent.get(pid, fn(state) -> + state.js_modules + |> Enum.filter(fn + {_, nil} -> false + _ -> true + end) + end) + end + + def list_foreign_modules(pid) do + Agent.get(pid, fn(state) -> + state.modules + |> Enum.filter(fn + (%{attributes: [__foreign_info__: _]}) -> true + (_) -> false + end) + end) + end + def list_modules(pid) do Agent.get(pid, fn(state) -> state.modules diff --git a/src/javascript/lib/core.js b/src/javascript/lib/core.js index bacfe6fe..b1c85c9e 100644 --- a/src/javascript/lib/core.js +++ b/src/javascript/lib/core.js @@ -5,6 +5,7 @@ import SpecialForms from './core/special_forms'; import erlang from './core/erlang_compat/erlang'; import maps from './core/erlang_compat/maps'; import lists from './core/erlang_compat/lists'; +import Store from './core/store'; class Integer {} class Float {} @@ -36,8 +37,9 @@ export default { Float, Functions, SpecialForms, + Store, global: globalState, erlang, maps, - lists, + lists }; diff --git a/src/javascript/lib/core/store.js b/src/javascript/lib/core/store.js new file mode 100644 index 00000000..f0ebc0ec --- /dev/null +++ b/src/javascript/lib/core/store.js @@ -0,0 +1,47 @@ +import Core from '../core'; + +function get_key(key) { + let real_key = key; + + if (__elixirscript_names__.has(key)) { + real_key = __elixirscript_names__.get(key); + } + + if (__elixirscript_store__.has(real_key)) { + return real_key; + } + + throw new Error(`Key ${real_key} not found`); +} + +function create(value, name = null) { + const key = new Core.PID(); + + if (name !== null) { + __elixirscript_names__.set(name, key); + } + + __elixirscript_store__.set(key, value); +} + +function update(key, value) { + const real_key = get_key(key); + __elixirscript_store__.set(real_key, value); +} + +function read(key) { + const real_key = get_key(key); + __elixirscript_store__.get(real_key); +} + +function remove(key) { + const real_key = get_key(key); + __elixirscript_store__.delete(real_key); +} + +export default { + create, + update, + read, + remove +}; diff --git a/test/support/json.ex b/test/support/json.ex new file mode 100644 index 00000000..a3c7e349 --- /dev/null +++ b/test/support/json.ex @@ -0,0 +1,6 @@ +defmodule JSON do + use ElixirScript.FFI + + foreign stringify(map) + foreign parse(string) +end \ No newline at end of file diff --git a/test/support/main.ex b/test/support/main.ex index 64562538..d32cd32e 100644 --- a/test/support/main.ex +++ b/test/support/main.ex @@ -2,6 +2,8 @@ defmodule Main do def start(:normal, [callback]) do callback.("started") + JSON.stringify(%{}) + Enum.each(1..3, fn x -> JS.console.log(x) end) end end From f27942b975ad8825a1b5004b1fd5bd9f61ed68c7 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Wed, 12 Jul 2017 09:00:38 -0500 Subject: [PATCH 02/11] Updated FFI to always assume there will be a js module at an assumed path --- lib/elixir_script/ffi.ex | 10 +++++-- lib/elixir_script/module_systems/common.ex | 6 ++-- lib/elixir_script/module_systems/es.ex | 16 ++-------- lib/elixir_script/module_systems/umd.ex | 4 +-- lib/elixir_script/passes/output.ex | 6 ++++ lib/elixir_script/passes/output/js_module.ex | 29 +------------------ .../passes/translate/forms/remote.ex | 6 ++-- lib/elixir_script/passes/translate/module.ex | 4 +-- lib/elixir_script/state.ex | 22 +++++++------- test/support/json.ex | 2 +- 10 files changed, 41 insertions(+), 64 deletions(-) diff --git a/lib/elixir_script/ffi.ex b/lib/elixir_script/ffi.ex index ff14b80b..890a6913 100644 --- a/lib/elixir_script/ffi.ex +++ b/lib/elixir_script/ffi.ex @@ -1,9 +1,15 @@ defmodule ElixirScript.FFI do - defmacro __using__(opts) do + defmacro __using__(_) do + js_path = Path.join([".", Macro.underscore(__MODULE__)]) + + js_name = __MODULE__ + |> Module.split() + |> Enum.join("_") + quote do import ElixirScript.FFI Module.register_attribute __MODULE__, :__foreign_info__, persist: true - @__foreign_info__ %{path: unquote(Keyword.get(opts, :path, nil))} + @__foreign_info__ %{path: unquote(js_path), name: unquote(js_name)} end end diff --git a/lib/elixir_script/module_systems/common.ex b/lib/elixir_script/module_systems/common.ex index 3236f92b..2f4da76b 100644 --- a/lib/elixir_script/module_systems/common.ex +++ b/lib/elixir_script/module_systems/common.ex @@ -7,7 +7,7 @@ defmodule ElixirScript.ModuleSystems.Common do imports = js_imports |> Enum.map(fn - {module, path} -> import_module(module, path) + {_module, name, path} -> import_module(name, path) end) imports = Enum.uniq(imports ++ module_imports) @@ -16,8 +16,8 @@ defmodule ElixirScript.ModuleSystems.Common do imports ++ body ++ export end - defp import_module(module_name, from) do - js_module_name = ElixirScript.Translate.Identifier.make_namespace_members(module_name) + defp import_module(name, from) do + js_module_name = JS.identifier(name) do_import_module(js_module_name, from) end diff --git a/lib/elixir_script/module_systems/es.ex b/lib/elixir_script/module_systems/es.ex index 3e92049c..72cd042a 100644 --- a/lib/elixir_script/module_systems/es.ex +++ b/lib/elixir_script/module_systems/es.ex @@ -7,8 +7,7 @@ defmodule ElixirScript.ModuleSystems.ES do imports = js_imports |> Enum.map(fn - {module, path} -> import_module(module, path) - {module, path, default: false} -> import_namespace_module(module, path) + {_module, name, path} -> import_module(name, path) end) imports = Enum.uniq(imports ++ module_imports) @@ -17,19 +16,8 @@ defmodule ElixirScript.ModuleSystems.ES do imports ++ body ++ export end - defp import_namespace_module(module_name, from) do - js_module_name = ElixirScript.Translate.Identifier.make_namespace_members(module_name) - - import_specifier = JS.import_namespace_specifier( - js_module_name, - js_module_name - ) - - do_import_module([import_specifier], from) - end - defp import_module(import_name, from) do - js_module_name = ElixirScript.Translate.Identifier.make_namespace_members(import_name) + js_module_name = JS.identifier(import_name) import_specifier = JS.import_default_specifier( js_module_name diff --git a/lib/elixir_script/module_systems/umd.ex b/lib/elixir_script/module_systems/umd.ex index e9894c24..fcd30795 100644 --- a/lib/elixir_script/module_systems/umd.ex +++ b/lib/elixir_script/module_systems/umd.ex @@ -9,7 +9,7 @@ defmodule ElixirScript.ModuleSystems.UMD do imports = js_imports |> Enum.map(fn - {module, path} -> import_module(module, path) + {_module, name, path} -> import_module(name, path) end) imports = Enum.uniq(imports ++ module_imports) @@ -19,7 +19,7 @@ defmodule ElixirScript.ModuleSystems.UMD do end defp import_module(module_name, from) do - js_module_name = ElixirScript.Translate.Identifier.make_namespace_members(module_name) + js_module_name = JS.identifier(module_name) {js_module_name, JS.literal(from)} end diff --git a/lib/elixir_script/passes/output.ex b/lib/elixir_script/passes/output.ex index a2d3e2fc..26522a4b 100644 --- a/lib/elixir_script/passes/output.ex +++ b/lib/elixir_script/passes/output.ex @@ -19,6 +19,12 @@ defmodule ElixirScript.Output do opts = ModuleState.get_compiler_opts(pid) + #TODO: Combine Mix.Project.config()[:app] with Mix.Project.deps_paths() to + # get app names. + # File.exists? Path.join([:code.priv_dir(app), "src", "elixir_script"]) + # to find out if app has interop files. + # If so, copy files and directories to output folder + bundle(modules, opts, ModuleState.js_modules(pid)) |> output(Map.get(opts, :output)) end diff --git a/lib/elixir_script/passes/output/js_module.ex b/lib/elixir_script/passes/output/js_module.ex index 79f22a83..a5cea45f 100644 --- a/lib/elixir_script/passes/output/js_module.ex +++ b/lib/elixir_script/passes/output/js_module.ex @@ -11,15 +11,10 @@ defmodule ElixirScript.Output.JSModule do elixir = J.variable_declaration([declarator], :const) - table_additions = Enum.map(js_modules, fn - {module, _} -> add_import_to_table(module) - {module, _, _} -> add_import_to_table(module) - end) - ast = opts.module_formatter.build( [], js_modules, - [elixir, create_atom_table(), start(), load()] ++ table_additions ++ body, + [elixir, create_atom_table(), start(), load()] ++ body, J.identifier("Elixir") ) @@ -99,26 +94,4 @@ defmodule ElixirScript.Output.JSModule do ) end - defp add_import_to_table(module_name) do - ref = ElixirScript.Translate.Identifier.make_namespace_members(module_name) - J.assignment_expression( - :=, - J.member_expression( - J.member_expression( - J.identifier("Elixir"), - J.identifier("__table__") - ), - J.call_expression( - J.member_expression( - J.identifier("Symbol"), - J.identifier("for") - ), - [J.literal(ref.name)] - ), - true - ), - ref - ) - end - end \ No newline at end of file diff --git a/lib/elixir_script/passes/translate/forms/remote.ex b/lib/elixir_script/passes/translate/forms/remote.ex index 93b186cc..c0ea3363 100644 --- a/lib/elixir_script/passes/translate/forms/remote.ex +++ b/lib/elixir_script/passes/translate/forms/remote.ex @@ -3,6 +3,7 @@ defmodule ElixirScript.Translate.Forms.Remote do alias ESTree.Tools.Builder, as: J alias ElixirScript.Translate.{Form, Identifier} + alias ElixirScript.State, as: ModuleState @erlang_modules [ :erlang, @@ -166,8 +167,9 @@ defmodule ElixirScript.Translate.Forms.Remote do Form.compile!(module, state) end - defp process_js_module_name(module, _) do - Identifier.make_namespace_members(module) + defp process_js_module_name(module, state) do + name = ModuleState.get_js_module_name(state.pid, module) + J.identifier(name) end defp erlang_compat_function(module, function) do diff --git a/lib/elixir_script/passes/translate/module.ex b/lib/elixir_script/passes/translate/module.ex index 049bfda0..df98c5c1 100644 --- a/lib/elixir_script/passes/translate/module.ex +++ b/lib/elixir_script/passes/translate/module.ex @@ -12,8 +12,8 @@ defmodule ElixirScript.Translate.Module do ElixirScript.Translate.Protocol.compile(module, info, pid) end - def compile(module, %{attributes: [__foreign_info__: %{path: path}]}, pid) do - ModuleState.put_javascript_module(pid, module, path) + def compile(module, %{attributes: [__foreign_info__: %{path: path, name: name}]}, pid) do + ModuleState.put_javascript_module(pid, name, path) nil end diff --git a/lib/elixir_script/state.ex b/lib/elixir_script/state.ex index 464f43eb..ed47108d 100644 --- a/lib/elixir_script/state.ex +++ b/lib/elixir_script/state.ex @@ -58,10 +58,10 @@ defmodule ElixirScript.State do end) end - def put_javascript_module(pid, module, path) do + def put_javascript_module(pid, module, name, path) do Agent.update(pid, fn(state) -> js_modules = Map.get(state, :js_modules, []) - js_modules = js_modules ++ [{module, path}] + js_modules = js_modules ++ [{module, name, path}] %{ state | js_modules: js_modules } end) @@ -71,10 +71,8 @@ defmodule ElixirScript.State do Agent.get(pid, fn(state) -> state.js_modules |> Enum.map(fn - {module_name, _path} -> - module_name - {module_name, _path, _opts} -> - module_name + {module, _name, _path} -> + module end) end) end @@ -82,10 +80,14 @@ defmodule ElixirScript.State do def js_modules(pid) do Agent.get(pid, fn(state) -> state.js_modules - |> Enum.filter(fn - {_, nil} -> false - _ -> true - end) + end) + end + + def get_js_module_name(pid, module) do + Agent.get(pid, fn(state) -> + {_, name, _} = state.js_modules + |> Enum.find(fn {m, _, _} -> module == m end) + name end) end diff --git a/test/support/json.ex b/test/support/json.ex index a3c7e349..d87ff485 100644 --- a/test/support/json.ex +++ b/test/support/json.ex @@ -1,4 +1,4 @@ -defmodule JSON do +defmodule Data.JSON do use ElixirScript.FFI foreign stringify(map) From b15c487e2d29f0d3c74f7a04cb9046a2ad437ff2 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Thu, 13 Jul 2017 21:40:44 -0500 Subject: [PATCH 03/11] Add global option to FFI. Fixed copy of js files to output path --- lib/elixir_script/cli.ex | 33 +---------- lib/elixir_script/compiler.ex | 1 - lib/elixir_script/ffi.ex | 14 ++--- lib/elixir_script/lib/store.ex | 2 +- lib/elixir_script/lib/string.ex | 6 +- lib/elixir_script/passes/find_used_modules.ex | 14 ++++- lib/elixir_script/passes/output.ex | 55 +++++++++++++++---- .../passes/translate/forms/remote.ex | 9 ++- lib/elixir_script/passes/translate/module.ex | 7 ++- lib/elixir_script/state.ex | 1 - lib/mix/tasks/compile.elixir_script.ex | 3 +- .../lib/core/erlang_compat/erlang.js | 12 +++- test/cli_test.exs | 6 -- test/passes/translate/forms/js_test.exs | 38 ------------- test/support/main.ex | 6 +- 15 files changed, 93 insertions(+), 114 deletions(-) diff --git a/lib/elixir_script/cli.ex b/lib/elixir_script/cli.ex index fd27b355..79ffcd80 100644 --- a/lib/elixir_script/cli.ex +++ b/lib/elixir_script/cli.ex @@ -8,8 +8,7 @@ defmodule ElixirScript.CLI do help: :boolean, version: :boolean, watch: :boolean, - format: :string, - js_module: [:string, :keep] + format: :string ] @aliases [ @@ -43,8 +42,6 @@ defmodule ElixirScript.CLI do the entry module of your application options: - --js-module [:] A js module used in your code. ex: React:react - Multiple can be defined -f --format [format] module format of output. options: es (default), common, umd -o --output [path] places output at the given path. Can be a directory or filename. @@ -72,13 +69,9 @@ defmodule ElixirScript.CLI do def do_process(input, options) do {watch, options} = Keyword.pop(options, :watch, false) - js_modules = Keyword.get_values(options, :js_module) - |> build_js_modules - compile_opts = [ output: Keyword.get(options, :output, :stdout), - format: String.to_atom(Keyword.get(options, :format, "es")), - js_modules: js_modules, + format: String.to_atom(Keyword.get(options, :format, "es")) ] input = handle_input(input) @@ -106,26 +99,4 @@ defmodule ElixirScript.CLI do |> List.flatten |> Enum.map(fn(x) -> Module.concat([x]) end) end - - defp build_js_modules(values) do - values - |> Enum.map(fn x -> - [identifier, path] = String.split(x, ":", trim: true) - { format_identifier(identifier), format_path(path) } - end) - end - - defp format_identifier(id) do - id - |> String.split(".") - |> Module.concat - end - - - defp format_path(path) do - path - |> String.replace("\"", "") - |> String.replace("`", "") - |> String.replace("'", "") - end end diff --git a/lib/elixir_script/compiler.ex b/lib/elixir_script/compiler.ex index 79fe7238..1c2d9c85 100644 --- a/lib/elixir_script/compiler.ex +++ b/lib/elixir_script/compiler.ex @@ -39,7 +39,6 @@ defmodule ElixirScript.Compiler do default_options = Map.new |> Map.put(:output, Keyword.get(opts, :output)) |> Map.put(:format, Keyword.get(opts, :format, :es)) - |> Map.put(:js_modules, Keyword.get(opts, :js_modules, [])) |> Map.put(:entry_modules, entry_modules) options = default_options diff --git a/lib/elixir_script/ffi.ex b/lib/elixir_script/ffi.ex index 890a6913..80114588 100644 --- a/lib/elixir_script/ffi.ex +++ b/lib/elixir_script/ffi.ex @@ -1,15 +1,13 @@ defmodule ElixirScript.FFI do - defmacro __using__(_) do - js_path = Path.join([".", Macro.underscore(__MODULE__)]) - - js_name = __MODULE__ - |> Module.split() - |> Enum.join("_") - + defmacro __using__(opts) do quote do import ElixirScript.FFI Module.register_attribute __MODULE__, :__foreign_info__, persist: true - @__foreign_info__ %{path: unquote(js_path), name: unquote(js_name)} + @__foreign_info__ %{ + path: Macro.underscore(__MODULE__), + name: Enum.join(Module.split(__MODULE__), "_"), + global: unquote(Keyword.get(opts, :global, false)) + } end end diff --git a/lib/elixir_script/lib/store.ex b/lib/elixir_script/lib/store.ex index dd6b4a7a..14447d9d 100644 --- a/lib/elixir_script/lib/store.ex +++ b/lib/elixir_script/lib/store.ex @@ -1,6 +1,6 @@ defmodule Bootstrap.Core.Store do @moduledoc false - use ElixirScript.FFI + use ElixirScript.FFI, global: true foreign create(value, name \\ nil) diff --git a/lib/elixir_script/lib/string.ex b/lib/elixir_script/lib/string.ex index 1ad3d9bf..d692f815 100644 --- a/lib/elixir_script/lib/string.ex +++ b/lib/elixir_script/lib/string.ex @@ -15,15 +15,15 @@ defmodule ElixirScript.String do end def to_float(str) do - JS.parseFloat(str) + :erlang.binary_to_float(str) end def to_integer(str) do - JS.parseInt(str, 10) + :erlang.binary_to_integer(str) end def to_integer(str, base) do - JS.parseInt(str, base) + :erlang.binary_to_integer(str, base) end def upcase(str) do diff --git a/lib/elixir_script/passes/find_used_modules.ex b/lib/elixir_script/passes/find_used_modules.ex index ccd94136..e352a935 100644 --- a/lib/elixir_script/passes/find_used_modules.ex +++ b/lib/elixir_script/passes/find_used_modules.ex @@ -25,6 +25,16 @@ defmodule ElixirScript.FindUsedModules do end end + defp walk_module(module, %{attributes: [__foreign_info__: %{path: path, name: name, global: global}]} = info, pid) do + path = if global, do: nil, else: path + name = if global, do: module, else: name + + ModuleState.put_javascript_module(pid, module, name, path) + ModuleState.put_module(pid, module, info) + + nil + end + defp walk_module(module, info, pid) do %{ attributes: _attrs, @@ -232,7 +242,7 @@ defmodule ElixirScript.FindUsedModules do walk({function, [], params}, state) end - defp walk({{:., _, [module, function]} = ast, _, params}, state) do + defp walk({{:., _, [_module, _function]} = ast, _, params}, state) do walk(ast, state) walk(params, state) end @@ -243,8 +253,6 @@ defmodule ElixirScript.FindUsedModules do defp walk({:., _, [module, function]}, state) do cond do - ElixirScript.Translate.Module.is_js_module(module, state) -> - nil ElixirScript.Translate.Module.is_elixir_module(module) -> if ModuleState.get_module(state.pid, module) == nil do execute(module, state.pid) diff --git a/lib/elixir_script/passes/output.ex b/lib/elixir_script/passes/output.ex index 26522a4b..7b6575b1 100644 --- a/lib/elixir_script/passes/output.ex +++ b/lib/elixir_script/passes/output.ex @@ -19,14 +19,18 @@ defmodule ElixirScript.Output do opts = ModuleState.get_compiler_opts(pid) - #TODO: Combine Mix.Project.config()[:app] with Mix.Project.deps_paths() to - # get app names. - # File.exists? Path.join([:code.priv_dir(app), "src", "elixir_script"]) - # to find out if app has interop files. - # If so, copy files and directories to output folder - - bundle(modules, opts, ModuleState.js_modules(pid)) - |> output(Map.get(opts, :output)) + js_modules = ModuleState.js_modules(pid) + |> Enum.filter(fn + {_module, _name, nil} -> false + _ -> true + end) + |> Enum.map(fn + {module, name, path} -> + {module, name, Path.join(".", path)} + end) + + bundle(modules, opts, js_modules) + |> output(Map.get(opts, :output), js_modules) end defp bundle(modules, opts, js_modules) do @@ -61,21 +65,27 @@ defmodule ElixirScript.Output do end end - defp output(code, nil) do + defp output(code, nil, _) do code end - defp output(code, :stdout) do + defp output(code, :stdout, _) do IO.puts(code) end - defp output(code, path) do + defp output(code, path, js_modules) do file_name = get_output_file_name(path) if !File.exists?(Path.dirname(file_name)) do File.mkdir_p!(Path.dirname(file_name)) end + apps = get_app_names() + output_dir = Path.dirname(file_name) + Enum.each(js_modules, fn({_, _, path}) -> + copy_javascript_module(apps, output_dir, path) + end) + File.write!(file_name, code) end @@ -87,4 +97,27 @@ defmodule ElixirScript.Output do Path.join([path, @generated_name]) end end + + defp get_app_names() do + Mix.Project.config()[:app] + deps = Mix.Project.deps_paths() + |> Map.keys + + [Mix.Project.config()[:app]] ++ deps + end + + defp copy_javascript_module(apps, output_dir, js_module_path) do + Enum.each(apps, fn(app) -> + full_path = Path.join([:code.priv_dir(app), "elixir_script", js_module_path]) <> ".js" + + if File.exists?(full_path) do + js_output_path = Path.join(output_dir, js_module_path) <> ".js" + if !File.exists?(Path.dirname(js_output_path)) do + File.mkdir_p!(Path.dirname(js_output_path)) + end + + File.cp(full_path, js_output_path) + end + end) + end end \ No newline at end of file diff --git a/lib/elixir_script/passes/translate/forms/remote.ex b/lib/elixir_script/passes/translate/forms/remote.ex index c0ea3363..8651e557 100644 --- a/lib/elixir_script/passes/translate/forms/remote.ex +++ b/lib/elixir_script/passes/translate/forms/remote.ex @@ -168,8 +168,13 @@ defmodule ElixirScript.Translate.Forms.Remote do end defp process_js_module_name(module, state) do - name = ModuleState.get_js_module_name(state.pid, module) - J.identifier(name) + case ModuleState.get_js_module_name(state.pid, module) do + name when is_atom(name) -> + members = Module.split(module) + Identifier.make_namespace_members(members) + name -> + J.identifier(name) + end end defp erlang_compat_function(module, function) do diff --git a/lib/elixir_script/passes/translate/module.ex b/lib/elixir_script/passes/translate/module.ex index df98c5c1..7dde8c3e 100644 --- a/lib/elixir_script/passes/translate/module.ex +++ b/lib/elixir_script/passes/translate/module.ex @@ -12,8 +12,11 @@ defmodule ElixirScript.Translate.Module do ElixirScript.Translate.Protocol.compile(module, info, pid) end - def compile(module, %{attributes: [__foreign_info__: %{path: path, name: name}]}, pid) do - ModuleState.put_javascript_module(pid, name, path) + def compile(module, %{attributes: [__foreign_info__: %{path: path, name: name, global: global}]}, pid) do + path = if global, do: nil, else: path + name = if global, do: module, else: name + + ModuleState.put_javascript_module(pid, module, name, path) nil end diff --git a/lib/elixir_script/state.ex b/lib/elixir_script/state.ex index ed47108d..50ff4cb7 100644 --- a/lib/elixir_script/state.ex +++ b/lib/elixir_script/state.ex @@ -62,7 +62,6 @@ defmodule ElixirScript.State do Agent.update(pid, fn(state) -> js_modules = Map.get(state, :js_modules, []) js_modules = js_modules ++ [{module, name, path}] - %{ state | js_modules: js_modules } end) end diff --git a/lib/mix/tasks/compile.elixir_script.ex b/lib/mix/tasks/compile.elixir_script.ex index b01abbcb..1e6067e2 100644 --- a/lib/mix/tasks/compile.elixir_script.ex +++ b/lib/mix/tasks/compile.elixir_script.ex @@ -65,8 +65,7 @@ defmodule Mix.Tasks.Compile.ElixirScript do input = Keyword.fetch!(elixirscript_config, :input) opts = [ output: Keyword.get(elixirscript_config, :output), - format: Keyword.get(elixirscript_config, :format), - js_modules: Keyword.get(elixirscript_config, :js_modules, []) + format: Keyword.get(elixirscript_config, :format) ] {input, opts} diff --git a/src/javascript/lib/core/erlang_compat/erlang.js b/src/javascript/lib/core/erlang_compat/erlang.js index aefbf7b7..5a0bc2d4 100644 --- a/src/javascript/lib/core/erlang_compat/erlang.js +++ b/src/javascript/lib/core/erlang_compat/erlang.js @@ -309,6 +309,14 @@ function tuple_size(tuple) { return tuple.length; } +function binary_to_float(str) { + return parseFloat(str); +} + +function binary_to_integer(str, base = 10) { + return parseInt(str, base); +} + export default { atom_to_binary, binary_to_atom, @@ -372,5 +380,7 @@ export default { round, tl, trunc, - tuple_size + tuple_size, + binary_to_float, + binary_to_integer }; diff --git a/test/cli_test.exs b/test/cli_test.exs index 53589cc9..b08c1d93 100644 --- a/test/cli_test.exs +++ b/test/cli_test.exs @@ -30,10 +30,4 @@ defmodule ElixirScript.CLI.Test do ElixirScript.CLI.process({["Atom"], []}) end) =~ "export default Elixir" end - - test "process js modules" do - assert capture_io(fn -> - ElixirScript.CLI.process({["Atom"], [js_module: "React:react"]}) - end) =~ "import React from 'react'" - end end \ No newline at end of file diff --git a/test/passes/translate/forms/js_test.exs b/test/passes/translate/forms/js_test.exs index c6d7c121..31bf2af3 100644 --- a/test/passes/translate/forms/js_test.exs +++ b/test/passes/translate/forms/js_test.exs @@ -64,42 +64,4 @@ defmodule ElixirScript.Translate.Forms.JS.Test do [J.literal("react")] ) end - - test "global function or property" do - ast = {{:., [], [JS, :self]}, [], []} - state = %{function: {:each, nil}, module: Enum, vars: %{:_ => 0, "entry" => 0, "enumerable" => 0, "fun" => 0}} - - {js_ast, _} = Form.compile(ast, state) - assert js_ast == J.call_expression( - ElixirScript.Translate.Forms.JS.call_property(), - [ - ElixirScript.Translate.Forms.JS.global(), - J.literal("self") - ] - ) - end - - test "global function with params" do - ast = {{:., [], [JS, :self]}, [], ["something"]} - state = %{function: {:each, nil}, module: Enum, vars: %{:_ => 0, "entry" => 0, "enumerable" => 0, "fun" => 0}} - - {js_ast, _} = Form.compile(ast, state) - assert js_ast == J.call_expression( - J.identifier(:self), - [J.literal("something")] - ) - end - - test "JavaScript module call", %{state: state} do - ast = {{:., [], [JS.Object, :keys]}, [], [{:obj, [], nil}]} - - {js_ast, _} = Form.compile(ast, state) - assert js_ast == J.call_expression( - J.member_expression( - J.identifier("Object"), - J.identifier("keys") - ), - [J.identifier("obj")] - ) - end end \ No newline at end of file diff --git a/test/support/main.ex b/test/support/main.ex index d32cd32e..99e3158b 100644 --- a/test/support/main.ex +++ b/test/support/main.ex @@ -1,9 +1,7 @@ defmodule Main do def start(:normal, [callback]) do callback.("started") - - JSON.stringify(%{}) - - Enum.each(1..3, fn x -> JS.console.log(x) end) + Agent.start(fn() -> nil end) + Data.JSON.stringify(%{}) end end From ce9d1d3fd83ca5a2250b93a5fa17ee00b872b5aa Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Thu, 13 Jul 2017 21:55:47 -0500 Subject: [PATCH 04/11] Update documentation --- CHANGELOG.md | 8 ++++++++ lib/elixir_script/ffi.ex | 4 ++++ lib/elixir_script/passes/translate/function.ex | 3 +-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b484b9e3..e3f5598c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] + +### Added +- ElixirScript now has an FFI layer for interoperability with JavaScript. For more details, see documentation at `ElixirScript.FFI` + +### Changed +- Compiler has been completely rewritten. ElixirScript now requires Erlang 20+ and Elixir 1.5+ + ## [0.28.0] - 2017-06-11 ### Added diff --git a/lib/elixir_script/ffi.ex b/lib/elixir_script/ffi.ex index 80114588..9c8e0363 100644 --- a/lib/elixir_script/ffi.ex +++ b/lib/elixir_script/ffi.ex @@ -1,4 +1,8 @@ defmodule ElixirScript.FFI do + @moduledoc """ + The foreign function interface for interoperability with JavaScript. + """ + defmacro __using__(opts) do quote do import ElixirScript.FFI diff --git a/lib/elixir_script/passes/translate/function.ex b/lib/elixir_script/passes/translate/function.ex index 148fe2ad..ecd353cf 100644 --- a/lib/elixir_script/passes/translate/function.ex +++ b/lib/elixir_script/passes/translate/function.ex @@ -5,8 +5,7 @@ defmodule ElixirScript.Translate.Function do @moduledoc """ Translates the given Elixir function AST into the - equivalent JavaScript AST. Function names are - + equivalent JavaScript AST. """ def patterns_ast() do From f4d45872c0183254116ee7901c8c8e39ac0153cc Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Thu, 13 Jul 2017 22:26:01 -0500 Subject: [PATCH 05/11] Update documentation for FFI module --- lib/elixir_script/ffi.ex | 29 +++++++++++++++++++++++++++++ test/support/main.ex | 2 -- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/elixir_script/ffi.ex b/lib/elixir_script/ffi.ex index 9c8e0363..a8130194 100644 --- a/lib/elixir_script/ffi.ex +++ b/lib/elixir_script/ffi.ex @@ -1,6 +1,35 @@ defmodule ElixirScript.FFI do @moduledoc """ The foreign function interface for interoperability with JavaScript. + + Define foreign modules with `use ElixirScript.FFI`. + Next to define functions for the foreign module, use the `foreign` macro. + + Here is an example of a foreign module for a JSON module + + ```elixir + defmodule MyApp.JSON do + use ElixirScript.FFI + + foreign stringify(map) + foreign parse(string) + end + ``` + + Foreign modules must have an equivalent JavaScript module. + ElixirScript expects JavaScript modules to be in the `priv/elixir_script` directory. + These modules will be copied to the output directory upon compilation. + + In the example, a JavaScript file must be at `priv/elixir_script/my_app/json.js`. + It looks like this + ```javascript + export default { + stringify: JSON.stringify, + parse: JSON.parse + } + ``` + + The JavaScript module must export a default object with the foreign functions defined in the Elixir module """ defmacro __using__(opts) do diff --git a/test/support/main.ex b/test/support/main.ex index 99e3158b..a18d3d90 100644 --- a/test/support/main.ex +++ b/test/support/main.ex @@ -1,7 +1,5 @@ defmodule Main do def start(:normal, [callback]) do callback.("started") - Agent.start(fn() -> nil end) - Data.JSON.stringify(%{}) end end From 69c327451670ca596e1994db8fae242d240c99a0 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Fri, 14 Jul 2017 20:21:57 -0500 Subject: [PATCH 06/11] Update FFI documentation --- JavascriptInterop.md | 58 +++++++++++++--------------------------- lib/elixir_script/ffi.ex | 24 +++++++++++------ 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/JavascriptInterop.md b/JavascriptInterop.md index db0c0e45..de8bf745 100644 --- a/JavascriptInterop.md +++ b/JavascriptInterop.md @@ -13,55 +13,35 @@ JS.debugger() # Getting the type of a value JS.typeof(my_value) - -# Creating a new JavaScript Map -map = JS.new(JS.Map, []) ``` -### Accessing Global Objects, Functions, and Properties - -In order to interact with JavaScript things in the global scope, append "JS" to them. The global scope corresponds to whatever the global object is in the JavaScript environment you are in. For example, in a browser this would be `window` or `self`: +### Foreign Function Interface -```elixir -# Calling alert -JS.alert("hello") +ElixirScript calls JavaScript modules through a Foreign Function Interface (FFI). A foreign module is defined by creating a new module and adding `use ElixirScript.FFI` to it. -# Calling a method on Object -JS.Object.keys(my_object) +Here is an example of a foreign module for a JSON module -# Creating a new JavaScript Date -JS.new(JS.Date, []) +```elixir +defmodule MyApp.JSON do + use ElixirScript.FFI -# Getting the outer width -JS.outerWidth + foreign stringify(map) + foreign parse(string) +end ``` -### JavaScript modules - -ElixirScript can use JavaScript modules from the supported modules systems. -In order to do so, you must tell ElixirScript about them upfront. +Foreign modules map to JavaScript files that export functions defined with the `foreign` macro. +ElixirScript expects JavaScript modules to be in the `priv/elixir_script` directory. +These modules are copied to the output directory upon compilation. -If using ElixirScript in a mix project, you can do so inside of the ElixirScript configuration keyword list +For our example, a JavaScript file must be placed at `priv/elixir_script/my_app/json.js`. -```elixir - def project do - [ - app: :my_project, - elixir_script: [ - format: :es, - js_modules: [ - {React, "react"}, - {ReactDOM, "react-dom"} - ] - ] - ] - end -``` - -Interacting with these modules works the same as interacting with an ElixirScript module - -```elixir -React.createElement(...) +It looks like this +```javascript +export default { + stringify: JSON.stringify, + parse: JSON.parse +} ``` ## JavaScript Calling ElixirScript diff --git a/lib/elixir_script/ffi.ex b/lib/elixir_script/ffi.ex index a8130194..abffc29b 100644 --- a/lib/elixir_script/ffi.ex +++ b/lib/elixir_script/ffi.ex @@ -1,9 +1,9 @@ defmodule ElixirScript.FFI do @moduledoc """ - The foreign function interface for interoperability with JavaScript. + The foreign function interface for interacting with JavaScript - Define foreign modules with `use ElixirScript.FFI`. - Next to define functions for the foreign module, use the `foreign` macro. + To define a foreign module, make a new module and add `use ElixirScript.FFI`. to it + To define foreign functions, use the `foreign` macro. Here is an example of a foreign module for a JSON module @@ -16,11 +16,12 @@ defmodule ElixirScript.FFI do end ``` - Foreign modules must have an equivalent JavaScript module. + Foreign modules map to JavaScript files that export functions defined with the `foreign` macro. ElixirScript expects JavaScript modules to be in the `priv/elixir_script` directory. - These modules will be copied to the output directory upon compilation. + These modules are copied to the output directory upon compilation. + + For our example, a JavaScript file must be placed at `priv/elixir_script/my_app/json.js`. - In the example, a JavaScript file must be at `priv/elixir_script/my_app/json.js`. It looks like this ```javascript export default { @@ -28,8 +29,6 @@ defmodule ElixirScript.FFI do parse: JSON.parse } ``` - - The JavaScript module must export a default object with the foreign functions defined in the Elixir module """ defmacro __using__(opts) do @@ -44,6 +43,15 @@ defmodule ElixirScript.FFI do end end + @doc """ + Defines a JavaScript function to be called from Elixir modules + + To define a foreign function, pass the name and arguments to `foreign` + + ```elixir + foreign my_js_function(arg1, arg2, arg3) + ``` + """ defmacro foreign({name, _, args}) do quote do def unquote(name)(unquote_splicing(args)), do: nil From f5b09cb6e047f7b9d4c1b9fa31ff65fe9deead48 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Fri, 14 Jul 2017 20:44:43 -0500 Subject: [PATCH 07/11] Add test for FFI output. Refactor debug_info for ElixirScript replacements --- lib/elixir_script/beam.ex | 18 ++++-------------- test/compiler_test.exs | 8 +++++++- test/support/main.ex | 2 ++ 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/elixir_script/beam.ex b/lib/elixir_script/beam.ex index d8cb7213..e850d837 100644 --- a/lib/elixir_script/beam.ex +++ b/lib/elixir_script/beam.ex @@ -10,21 +10,11 @@ defmodule ElixirScript.Beam do @spec debug_info(atom) :: {:ok | :error, map | binary} def debug_info(module) - #Replacing String module with our ElixirScript's version - def debug_info(String) do - case debug_info(ElixirScript.String) do + #Replace some modules with ElixirScript versions + def debug_info(module) when module in [String, Agent] do + case debug_info(Module.concat(ElixirScript, module)) do {:ok, info} -> - {:ok, Map.put(info, :module, String)} - e -> - e - end - end - - #Replacing Agent module with our ElixirScript's version - def debug_info(Agent) do - case debug_info(ElixirScript.Agent) do - {:ok, info} -> - {:ok, Map.put(info, :module, Agent)} + {:ok, Map.put(info, :module, module)} e -> e end diff --git a/test/compiler_test.exs b/test/compiler_test.exs index c154e67c..5651ec05 100644 --- a/test/compiler_test.exs +++ b/test/compiler_test.exs @@ -6,8 +6,14 @@ defmodule ElixirScript.Compiler.Test do assert is_binary(result) end + test "Use defined module with FFI module" do + result = ElixirScript.Compiler.compile(Main) + assert is_binary(result) + assert result =~ "import Data_JSON from './data/json'" + end + test "Can compile multiple entry modules" do - result = ElixirScript.Compiler.compile([Atom, String]) + result = ElixirScript.Compiler.compile([Atom, String, Agent]) assert is_binary(result) end diff --git a/test/support/main.ex b/test/support/main.ex index a18d3d90..8cdf51d8 100644 --- a/test/support/main.ex +++ b/test/support/main.ex @@ -1,5 +1,7 @@ defmodule Main do def start(:normal, [callback]) do callback.("started") + + Data.JSON.stringify(1) end end From 95efb4b1b481b754d7b76698806ebd2a04d62e42 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Fri, 14 Jul 2017 20:47:20 -0500 Subject: [PATCH 08/11] Test FFI JavaScript file moved to output folder --- test/compiler_test.exs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/compiler_test.exs b/test/compiler_test.exs index 5651ec05..a2a3cce3 100644 --- a/test/compiler_test.exs +++ b/test/compiler_test.exs @@ -52,4 +52,12 @@ defmodule ElixirScript.Compiler.Test do result = ElixirScript.Compiler.compile(Atom, [output: path]) assert File.exists?(path) end + + test "Output with FFI" do + path = System.tmp_dir() + + result = ElixirScript.Compiler.compile(Main, [output: path]) + assert File.exists?(Path.join([path, "Elixir.App.js"])) + assert File.exists?(Path.join([path, "data", "json.js"])) + end end \ No newline at end of file From 43636b34351078e6de553a742a9ebafd86fefb77 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Sat, 15 Jul 2017 07:20:56 -0500 Subject: [PATCH 09/11] Remove test json ffi module --- test/compiler_test.exs | 14 -------------- test/support/json.ex | 6 ------ test/support/main.ex | 2 -- 3 files changed, 22 deletions(-) delete mode 100644 test/support/json.ex diff --git a/test/compiler_test.exs b/test/compiler_test.exs index a2a3cce3..f41aa7bf 100644 --- a/test/compiler_test.exs +++ b/test/compiler_test.exs @@ -6,12 +6,6 @@ defmodule ElixirScript.Compiler.Test do assert is_binary(result) end - test "Use defined module with FFI module" do - result = ElixirScript.Compiler.compile(Main) - assert is_binary(result) - assert result =~ "import Data_JSON from './data/json'" - end - test "Can compile multiple entry modules" do result = ElixirScript.Compiler.compile([Atom, String, Agent]) assert is_binary(result) @@ -52,12 +46,4 @@ defmodule ElixirScript.Compiler.Test do result = ElixirScript.Compiler.compile(Atom, [output: path]) assert File.exists?(path) end - - test "Output with FFI" do - path = System.tmp_dir() - - result = ElixirScript.Compiler.compile(Main, [output: path]) - assert File.exists?(Path.join([path, "Elixir.App.js"])) - assert File.exists?(Path.join([path, "data", "json.js"])) - end end \ No newline at end of file diff --git a/test/support/json.ex b/test/support/json.ex deleted file mode 100644 index d87ff485..00000000 --- a/test/support/json.ex +++ /dev/null @@ -1,6 +0,0 @@ -defmodule Data.JSON do - use ElixirScript.FFI - - foreign stringify(map) - foreign parse(string) -end \ No newline at end of file diff --git a/test/support/main.ex b/test/support/main.ex index 8cdf51d8..a18d3d90 100644 --- a/test/support/main.ex +++ b/test/support/main.ex @@ -1,7 +1,5 @@ defmodule Main do def start(:normal, [callback]) do callback.("started") - - Data.JSON.stringify(1) end end From 729927ce28c2ad6b766eede9ff9859eeddc28319 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Sat, 15 Jul 2017 07:41:58 -0500 Subject: [PATCH 10/11] Refactoring. Update dependencies --- lib/elixir_script/passes/translate/forms/remote.ex | 6 +----- lib/elixir_script/passes/translate/identifier.ex | 4 ---- mix.lock | 6 +++--- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/elixir_script/passes/translate/forms/remote.ex b/lib/elixir_script/passes/translate/forms/remote.ex index 8651e557..c09a065d 100644 --- a/lib/elixir_script/passes/translate/forms/remote.ex +++ b/lib/elixir_script/passes/translate/forms/remote.ex @@ -126,11 +126,7 @@ defmodule ElixirScript.Translate.Forms.Remote do end def compile({:., _, [module, function]}, state) do - function_name = if ElixirScript.Translate.Module.is_js_module(module, state) do - ElixirScript.Translate.Identifier.make_extern_function_name(function) - else - ElixirScript.Translate.Identifier.make_function_name(function) - end + function_name = ElixirScript.Translate.Identifier.make_function_name(function) ast = J.member_expression( process_module_name(module, state), diff --git a/lib/elixir_script/passes/translate/identifier.ex b/lib/elixir_script/passes/translate/identifier.ex index e91c02d2..a619f1fa 100644 --- a/lib/elixir_script/passes/translate/identifier.ex +++ b/lib/elixir_script/passes/translate/identifier.ex @@ -75,8 +75,4 @@ defmodule ElixirScript.Translate.Identifier do J.identifier(name) end - def make_extern_function_name(name) do - J.identifier("#{name}") - end - end diff --git a/mix.lock b/mix.lock index 9de5d64f..992c5f2f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,10 @@ %{"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "certifi": {:hex, :certifi, "1.2.1", "c3904f192bd5284e5b13f20db3ceac9626e14eeacfbb492e19583cf0e37b22be", [:rebar3], [], "hexpm"}, - "credo": {:hex, :credo, "0.8.1", "137efcc99b4bc507c958ba9b5dff70149e971250813cbe7d4537ec7e36997402", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, + "credo": {:hex, :credo, "0.8.3", "efe6e9078de64cefdd25d8df7a97292e29e63f42a8988990340eaf1f40d93224", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, "earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], [], "hexpm"}, "estree": {:hex, :estree, "2.6.0", "86a301b0c355fa55c19e7ef9dceb1b1e983c6df526a2b7846818a38c258fc3fb", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, - "excoveralls": {:hex, :excoveralls, "0.7.0", "05cb3332c2b0f799df3ab90eb7df1ae5a147c86776e91792848a12b7ed87242f", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.16.2", "3b3e210ebcd85a7c76b4e73f85c5640c011d2a0b2f06dcdf5acdb2ae904e5084", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, + "excoveralls": {:hex, :excoveralls, "0.7.1", "3dd659db19c290692b5e2c4a2365ae6d4488091a1ba58f62dcbdaa0c03da5491", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"}, "fs": {:hex, :fs, "3.4.0", "6d18575c250b415b3cad559e6f97a4c822516c7bc2c10bfbb2493a8f230f5132", [:rebar3], [], "hexpm"}, "hackney": {:hex, :hackney, "1.8.6", "21a725db3569b3fb11a6af17d5c5f654052ce9624219f1317e8639183de4a423", [:rebar3], [{:certifi, "1.2.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.0.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, From caa147895af9f0bd542354427ed1b31fd5e8309d Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Sat, 15 Jul 2017 07:51:58 -0500 Subject: [PATCH 11/11] Add ffi test --- test/ffi_test.exs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 test/ffi_test.exs diff --git a/test/ffi_test.exs b/test/ffi_test.exs new file mode 100644 index 00000000..dbbaa77d --- /dev/null +++ b/test/ffi_test.exs @@ -0,0 +1,17 @@ +defmodule ElixirScript.FFI.Test do + use ExUnit.Case + + defmodule MyTestModule do + use ElixirScript.FFI + + foreign my_test_function(arg1, arg2) + end + + test "FFI module has __foreign_info__ attribute" do + assert Keyword.has_key?(MyTestModule.__info__(:attributes), :__foreign_info__) + end + + test "FFI module makes foreign function" do + assert Keyword.has_key?(MyTestModule.__info__(:functions), :my_test_function) + end +end \ No newline at end of file