From 0fbec6ebb0f8c66538c170cd7a025105a82d0a9b Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Sun, 30 Aug 2015 17:56:53 -0500 Subject: [PATCH 01/15] Began working on allowing macros --- lib/elixir_script/translator.ex | 214 ++++++++++++--------- lib/elixir_script/translator/assignment.ex | 4 +- lib/elixir_script/translator/capture.ex | 6 +- lib/elixir_script/translator/case.ex | 4 +- lib/elixir_script/translator/expression.ex | 8 +- lib/elixir_script/translator/for.ex | 45 +++-- lib/elixir_script/translator/function.ex | 47 +++-- lib/elixir_script/translator/kernel.ex | 13 -- lib/elixir_script/translator/module.ex | 26 +-- lib/elixir_script/translator/primitive.ex | 12 +- lib/elixir_script/translator/receive.ex | 13 +- lib/elixir_script/translator/try.ex | 21 +- lib/elixir_script/translator/utils.ex | 32 +-- mix.lock | 2 +- test/test_helper.exs | 19 +- test/translator/assignment_test.exs | 4 +- test/translator/function_test.exs | 16 ++ 17 files changed, 267 insertions(+), 219 deletions(-) diff --git a/lib/elixir_script/translator.ex b/lib/elixir_script/translator.ex index 43cfaaef..ce1db316 100644 --- a/lib/elixir_script/translator.ex +++ b/lib/elixir_script/translator.ex @@ -27,70 +27,70 @@ defmodule ElixirScript.Translator do @doc """ Translates Elixir AST to JavaScript AST """ - def translate(ast) do - do_translate(ast) + def translate(ast, env \\ __ENV__) do + do_translate(ast, env) end - defp do_translate(ast) when is_number(ast) or is_binary(ast) or is_boolean(ast) or is_nil(ast) do + defp do_translate(ast, env) when is_number(ast) or is_binary(ast) or is_boolean(ast) or is_nil(ast) do Primitive.make_literal(ast) end - defp do_translate(ast) when is_atom(ast) do + defp do_translate(ast, env) when is_atom(ast) do Primitive.make_atom(ast) end - defp do_translate(ast) when is_list(ast) do - Primitive.make_list(ast) + defp do_translate(ast, env) when is_list(ast) do + Primitive.make_list(ast, env) end - defp do_translate({ one, two }) do - Primitive.make_tuple({one, two}) + defp do_translate({ one, two }, env) do + Primitive.make_tuple({one, two}, env) end - defp do_translate({:&, [], [number]}) when is_number(number) do + defp do_translate({:&, [], [number]}, env) when is_number(number) do Primitive.make_identifier(String.to_atom("__#{number}")) end - defp do_translate({:&, _, [{:/, _, [{{:., _, [{:__aliases__, _, module_name}, function_name]}, _, []}, arity]}]}) do + defp do_translate({:&, _, [{:/, _, [{{:., _, [{:__aliases__, _, module_name}, function_name]}, _, []}, arity]}]}, env) do function_name = Utils.filter_name(function_name) - Capture.make_capture(List.last(module_name), function_name, arity) + Capture.make_capture(List.last(module_name), function_name, arity, env) end - defp do_translate({:&, _, [{:/, _, [{function_name, _, _}, arity]}]}) do + defp do_translate({:&, _, [{:/, _, [{function_name, _, _}, arity]}]}, env) do function_name = Utils.filter_name(function_name) - Capture.make_capture(function_name, arity) + Capture.make_capture(function_name, arity, env) end - defp do_translate({:&, _, body}) do + defp do_translate({:&, _, body}, env) do params = Capture.find_value_placeholders(body) |> List.flatten - Function.make_anonymous_function([{:->, [], [params, body]}]) + Function.make_anonymous_function([{:->, [], [params, body]}], env) end - defp do_translate({:@, _, [{name, _, [value]}]}) do + defp do_translate({:@, _, [{name, _, [value]}]}, env) do name = Utils.filter_name(name) - Module.make_attribute(name, value) + Module.make_attribute(name, value, env) end - defp do_translate({:@, _, [{name, _, _}]}) do + defp do_translate({:@, _, [{name, _, _}]}, env) do name = Utils.filter_name(name) Primitive.make_identifier(name) end - defp do_translate({:%, _, [alias_info, data]}) do + defp do_translate({:%, _, [alias_info, data]}, env) do {_, _, name} = alias_info {_, _, data} = data Struct.make_struct(name, data) end - defp do_translate({:%{}, _, [{:|, _, [map, data]}]}) do + defp do_translate({:%{}, _, [{:|, _, [map, data]}]}, env) do Map.make_map_update(map, data); end - defp do_translate({:%{}, _, properties}) do + defp do_translate({:%{}, _, properties}, env) do Map.make_object(properties) end - defp do_translate({:<<>>, _, elements}) do + defp do_translate({:<<>>, _, elements}, env) do is_interpolated_string = Enum.all?(elements, fn(x) -> case x do b when is_binary(b) -> @@ -110,181 +110,202 @@ defmodule ElixirScript.Translator do end end - defp do_translate({{:., _, [Access, :get]}, _, [target, property]}) do + defp do_translate({{:., _, [Access, :get]}, _, [target, property]}, env) do Map.make_get_property(target, property) end - defp do_translate({:., _, [module_name, function_name]}) do - Function.make_function_or_property_call(module_name, function_name) + defp do_translate({:., _, [module_name, function_name]} = ast, env) do + expanded_ast = Macro.expand(ast, env) + if expanded_ast == ast do + Function.make_function_or_property_call(module_name, function_name, env) + else + translate(expanded_ast, env) + end end - defp do_translate({{:., _, [module_name, function_name]}, _, [] }) do - Function.make_function_or_property_call(module_name, function_name) + defp do_translate({{:., _, [module_name, function_name]}, _, [] } = ast, env) do + expanded_ast = Macro.expand(ast, env) + if expanded_ast == ast do + Function.make_function_or_property_call(module_name, function_name, env) + else + translate(expanded_ast, env) + end end - defp do_translate({{:., _, [{:__aliases__, _, module_name}]}, _, params}) do - Function.make_function_call(hd(module_name), params) + defp do_translate({{:., _, [{:__aliases__, _, module_name}]}, _, params} = ast, env) do + expanded_ast = Macro.expand(ast, env) + if expanded_ast == ast do + Function.make_function_call(hd(module_name), params, env) + else + translate(expanded_ast, env) + end end - defp do_translate({{:., _, [module_name, function_name]}, _, params }) do - Function.make_function_call(module_name, function_name, params) + defp do_translate({{:., _, [module_name, function_name]}, _, params } = ast, env) do + expanded_ast = Macro.expand(ast, env) + if expanded_ast == ast do + Function.make_function_call(module_name, function_name, params, env) + else + translate(expanded_ast, env) + end end - defp do_translate({:_, _, _}) do + defp do_translate({:_, _, _}, env) do Primitive.make_identifier(:undefined) end - defp do_translate({:__aliases__, _, aliases}) do + defp do_translate({:__aliases__, _, aliases}, env) do Primitive.make_identifier(aliases) end - defp do_translate({:__block__, _, expressions }) do + defp do_translate({:__block__, _, expressions }, env) do Block.make_block(expressions) end - defp do_translate({:__DIR__, _, _expressions }) do + defp do_translate({:__DIR__, _, _expressions }, env) do ExKernel.make___DIR__() end - defp do_translate({:try, _, [ blocks ]}) do - Try.make_try(blocks) + defp do_translate({:try, _, [ blocks ]}, env) do + Try.make_try(blocks, env) end - defp do_translate({:receive, _, [expressions] }) do - Receive.make_receive(expressions); + defp do_translate({:receive, _, [expressions] }, env) do + Receive.make_receive(expressions, env); end - defp do_translate({:super, _, _expressions }) do + defp do_translate({:super, _, _expressions }, env) do raise ElixirScript.UnsupportedError, :super end - defp do_translate({:__CALLER__, _, _expressions }) do + defp do_translate({:__CALLER__, _, _expressions }, env) do raise ElixirScript.UnsupportedError, :__CALLER__ end - defp do_translate({:__ENV__, _, _expressions }) do + defp do_translate({:__ENV__, _, _expressions }, env) do raise ElixirScript.UnsupportedError, :__ENV__ end - defp do_translate({:quote, _, [[do: expr]]}) do + defp do_translate({:quote, _, [[do: expr]]}, env) do Quote.make_quote([], expr) end - defp do_translate({:quote, _, [opts, [do: expr]]}) do + defp do_translate({:quote, _, [opts, [do: expr]]}, env) do Quote.make_quote(opts, expr) end - defp do_translate({:import, _, [{:__aliases__, _, module_name_list}]}) do + defp do_translate({:import, _, [{:__aliases__, _, module_name_list}]}, env) do Import.make_import(module_name_list, []) end - defp do_translate({:import, _, [{:__aliases__, _, module_name_list}, options ]}) do + defp do_translate({:import, _, [{:__aliases__, _, module_name_list}, options ]}, env) do Import.make_import(module_name_list, options) end - defp do_translate({:alias, _, [alias_info, options]}) do + defp do_translate({:alias, _, [alias_info, options]}, env) do Import.make_alias_import(alias_info, options) end - defp do_translate({:alias, _, [alias_info]}) do + defp do_translate({:alias, _, [alias_info]}, env) do Import.make_alias_import(alias_info, []) end - defp do_translate({:require, _, [alias_info, options]}) do + defp do_translate({:require, _, [alias_info, options]}, env) do Import.make_alias_import(alias_info, options) end - defp do_translate({:require, _, [alias_info]}) do + defp do_translate({:require, _, [alias_info]}, env) do Import.make_alias_import(alias_info, []) end - defp do_translate({:case, _, [condition, [do: clauses]]}) do - Case.make_case(condition, clauses) + defp do_translate({:case, _, [condition, [do: clauses]]}, env) do + Case.make_case(condition, clauses, env) end - defp do_translate({:cond, _, [[do: clauses]]}) do + defp do_translate({:cond, _, [[do: clauses]]}, env) do Cond.make_cond(clauses) end - defp do_translate({:for, _, generators}) do - For.make_for(generators) + defp do_translate({:for, _, generators}, env) do + For.make_for(generators, env) end - defp do_translate({:fn, _, clauses}) do - Function.make_anonymous_function(clauses) + defp do_translate({:fn, _, clauses}, env) do + Function.make_anonymous_function(clauses, env) end - defp do_translate({:.., _, [first, last]}) do + defp do_translate({:.., _, [first, last]}, env) do ExKernel.make_range(first, last) end - defp do_translate({:{}, _, elements}) do - Primitive.make_tuple(elements) + defp do_translate({:{}, _, elements}, env) do + Primitive.make_tuple(elements, env) end - defp do_translate({operator, _, [value]}) when operator in [:-, :!] do - Expression.make_unary_expression(operator, value) + defp do_translate({operator, _, [value]}, env) when operator in [:-, :!] do + Expression.make_unary_expression(operator, value, env) end - defp do_translate({:=, _, [left, right]}) do - Assignment.make_assignment(left, right) + defp do_translate({:=, _, [left, right]}, env) do + Assignment.make_assignment(left, right, env) end - defp do_translate({:<>, _, [left, right]}) do - Expression.make_binary_expression(:+, left, right) + defp do_translate({:<>, _, [left, right]}, env) do + Expression.make_binary_expression(:+, left, right, env) end - defp do_translate({:++, _, [left, right]}) do + defp do_translate({:++, _, [left, right]}, env) do ExKernel.concat_lists(left, right) end - defp do_translate({operator, _, [left, right]}) when operator in [:+, :-, :/, :*, :==, :!=, :&&, :||, :>, :<, :>=, :<=, :===] do - Expression.make_binary_expression(operator, left, right) + defp do_translate({operator, _, [left, right]}, env) when operator in [:+, :-, :/, :*, :==, :!=, :&&, :||, :>, :<, :>=, :<=, :===] do + Expression.make_binary_expression(operator, left, right, env) end - defp do_translate({:and, _, [left, right]}) do - Expression.make_binary_expression(:&&, left, right) + defp do_translate({:and, _, [left, right]}, env) do + Expression.make_binary_expression(:&&, left, right, env) end - defp do_translate({:or, _, [left, right]}) do - Expression.make_binary_expression(:||, left, right) + defp do_translate({:or, _, [left, right]}, env) do + Expression.make_binary_expression(:||, left, right, env) end - defp do_translate({function, _, [{:when, _, [{name, _, _params} | _guards] }, [do: _body]] } = ast) when function in [:def, :defp] do - Function.process_function(Utils.filter_name(name), [ast]) + defp do_translate({function, _, [{:when, _, [{name, _, _params} | _guards] }, [do: _body]] } = ast, env) when function in [:def, :defp] do + Function.process_function(Utils.filter_name(name), [ast], env) end - defp do_translate({function, _, [{name, _, _params}, [do: _body]]} = ast) when function in [:def, :defp] do - Function.process_function(Utils.filter_name(name), [ast]) + defp do_translate({function, _, [{name, _, _params}, [do: _body]]} = ast, env) when function in [:def, :defp] do + Function.process_function(Utils.filter_name(name), [ast], env) end - defp do_translate({:defstruct, _, attributes}) do + defp do_translate({:defstruct, _, attributes}, env) do Struct.make_defstruct(attributes) end - defp do_translate({:defexception, _, attributes}) do + defp do_translate({:defexception, _, attributes}, env) do Struct.make_defexception(attributes) end - defp do_translate({:raise, _, [alias_info, attributes]}) do + defp do_translate({:raise, _, [alias_info, attributes]}, env) do {_, _, name} = alias_info Raise.throw_error(name, attributes) end - defp do_translate({:raise, _, [message]}) do + defp do_translate({:raise, _, [message]}, env) do Raise.throw_error(message) end - defp do_translate({:if, _, [test, blocks]}) do - If.make_if(test, blocks) + defp do_translate({:if, _, _} = ast, env) do + Macro.expand(ast, env) + |> translate end - defp do_translate({:defmodule, _, [{:__aliases__, _, module_name_list}, [do: body]]}) do - Module.make_module(module_name_list, body) + defp do_translate({:defmodule, _, [{:__aliases__, _, module_name_list}, [do: body]]}, env) do + Module.make_module(module_name_list, body, env) end - defp do_translate({:|>, _, [left, right]}) do + defp do_translate({:|>, _, [left, right]}, env) do case right do {{:., meta, [module, fun]}, meta2, params} -> translate({{:., meta, [module, fun]}, meta2, [left] ++ params}) @@ -293,18 +314,23 @@ defmodule ElixirScript.Translator do end end - defp do_translate({name, metadata, params}) when is_list(params) do - name = Utils.filter_name(name) + defp do_translate({name, metadata, params} = ast, env) when is_list(params) do + expanded_ast = Macro.expand(ast, env) + if expanded_ast == ast do + name = Utils.filter_name(name) - case metadata[:import] do - Kernel -> - Function.make_function_call(:Kernel, name, params) - _ -> - Function.make_function_call(name, params) + case metadata[:import] do + Kernel -> + Function.make_function_call(:Kernel, name, params, env) + _ -> + Function.make_function_call(name, params, env) + end + else + translate(expanded_ast) end end - defp do_translate({ name, _, _ }) do + defp do_translate({ name, _, _ }, env) do name = Utils.filter_name(name) Primitive.make_identifier(name) end diff --git a/lib/elixir_script/translator/assignment.ex b/lib/elixir_script/translator/assignment.ex index a4be27a0..4787b9e2 100644 --- a/lib/elixir_script/translator/assignment.ex +++ b/lib/elixir_script/translator/assignment.ex @@ -5,7 +5,7 @@ defmodule ElixirScript.Translator.Assignment do alias ElixirScript.Translator alias ElixirScript.Translator.PatternMatching.Match - def make_assignment(left, right) do + def make_assignment(left, right, env) do { patterns, params } = Match.build_match([left]) declarator = JS.variable_declarator( @@ -15,7 +15,7 @@ defmodule ElixirScript.Translator.Assignment do JS.identifier("fun"), JS.identifier("bind") ), - [hd(patterns), Translator.translate(right)] + [hd(patterns), Translator.translate(right, env)] ) ) diff --git a/lib/elixir_script/translator/capture.ex b/lib/elixir_script/translator/capture.ex index 6221755d..8e7a3f2b 100644 --- a/lib/elixir_script/translator/capture.ex +++ b/lib/elixir_script/translator/capture.ex @@ -3,7 +3,7 @@ defmodule ElixirScript.Translator.Capture do alias ElixirScript.Translator.PatternMatching.Match alias ElixirScript.Translator.Utils - def make_capture(function_name, arity) do + def make_capture(function_name, arity, env) do params = Enum.map(1..arity, fn(x) -> {String.to_atom("__#{x}"), [], ElixirScript.Translator.Capture} end) { patterns, params } = Match.build_match(params) @@ -25,7 +25,7 @@ defmodule ElixirScript.Translator.Capture do ]) end - def make_capture(module_name, function_name, arity) do + def make_capture(module_name, function_name, arity, env) do params = Enum.map(1..arity, fn(x) -> {String.to_atom("__#{x}"), [], ElixirScript.Translator.Capture} end) { patterns, params } = Match.build_match(params) @@ -38,7 +38,7 @@ defmodule ElixirScript.Translator.Capture do JS.block_statement([ JS.return_statement( JS.call_expression( - Utils.make_member_expression(module_name, function_name), + Utils.make_member_expression(module_name, function_name, env), params ) ) diff --git a/lib/elixir_script/translator/case.ex b/lib/elixir_script/translator/case.ex index 65c27b4a..3a028bc7 100644 --- a/lib/elixir_script/translator/case.ex +++ b/lib/elixir_script/translator/case.ex @@ -4,10 +4,10 @@ defmodule ElixirScript.Translator.Case do alias ElixirScript.Translator alias ElixirScript.Translator.Function - def make_case(condition, clauses) do + def make_case(condition, clauses, env) do Builder.call_expression( Builder.member_expression( - Function.make_anonymous_function(clauses), + Function.make_anonymous_function(clauses, env), Builder.identifier("call") ), [Translator.translate(condition)] diff --git a/lib/elixir_script/translator/expression.ex b/lib/elixir_script/translator/expression.ex index 8446e647..062d45a7 100644 --- a/lib/elixir_script/translator/expression.ex +++ b/lib/elixir_script/translator/expression.ex @@ -4,12 +4,12 @@ defmodule ElixirScript.Translator.Expression do alias ESTree.Tools.Builder alias ElixirScript.Translator - def make_unary_expression(operator, expr) do - Builder.unary_expression(operator, true, Translator.translate(expr)) + def make_unary_expression(operator, expr, env) do + Builder.unary_expression(operator, true, Translator.translate(expr, env)) end - def make_binary_expression(operator, left, right) do - Builder.binary_expression(operator, Translator.translate(left), Translator.translate(right)) + def make_binary_expression(operator, left, right, env) do + Builder.binary_expression(operator, Translator.translate(left, env), Translator.translate(right, env)) end end \ No newline at end of file diff --git a/lib/elixir_script/translator/for.ex b/lib/elixir_script/translator/for.ex index e6d287fa..53678976 100644 --- a/lib/elixir_script/translator/for.ex +++ b/lib/elixir_script/translator/for.ex @@ -5,60 +5,63 @@ defmodule ElixirScript.Translator.For do alias ElixirScript.Translator.Utils - def make_for(generators) do - _results = Translator.translate(quote do: _results) - variable_declaration = Translator.translate(quote do: _results = []) + def make_for(generators, env) do + quoted = quote do: _results + _results = Translator.translate(quoted, env) - block_statement = [variable_declaration] ++ [handle_generators(generators)] ++ [Builder.return_statement(_results)] + quoted = quote do: _results = [] + variable_declaration = Translator.translate(quoted, env) + + block_statement = [variable_declaration] ++ [handle_generators(generators, env)] ++ [Builder.return_statement(_results)] Utils.wrap_in_function_closure(block_statement) end - defp handle_generators(generators) do + defp handle_generators(generators, env) do case hd(generators) do {:<-, [], [identifier, enum]} -> case identifier do {value_one, value_two} -> elements = [value_one, value_two] - make_tuple_for(elements, enum, generators) + make_tuple_for(elements, enum, generators, env) {:{}, _, elements} -> - make_tuple_for(elements, enum, generators) + make_tuple_for(elements, enum, generators, env) _ -> - i = Translator.translate(identifier) + i = Translator.translate(identifier, env) variable_declarator = Builder.variable_declarator(i) variable_declaration = Builder.variable_declaration([variable_declarator], :let) Builder.for_of_statement( variable_declaration, - Translator.translate(enum), - Builder.block_statement(List.wrap(handle_generators(tl(generators)))) + Translator.translate(enum, env), + Builder.block_statement(List.wrap(handle_generators(tl(generators), env))) ) end [into: _expression] -> raise ElixirScript.UnsupportedError, :into [do: expression] -> - push_last_expression(Translator.translate(expression)) + push_last_expression(Translator.translate(expression, env)) filter -> Builder.if_statement( - Translator.translate(filter), - handle_generators(tl(generators)), + Translator.translate(filter, env), + handle_generators(tl(generators), env), nil ) end end - defp make_tuple_for(elements, enum, generators) do + defp make_tuple_for(elements, enum, generators, env) do i = Builder.identifier("_ref") variable_declarator = Builder.variable_declarator(i) variable_declaration = Builder.variable_declaration([variable_declarator], :let) { variables, _ } = Enum.map_reduce(elements, 0, fn(x, index) -> - case Translator.translate(x) do + case Translator.translate(x, env) do %ESTree.Identifier{} -> - variable_declarator = Builder.variable_declarator(Translator.translate(x), + variable_declarator = Builder.variable_declarator(Translator.translate(x, env), Builder.call_expression( Builder.member_expression( Builder.identifier(:Kernel), @@ -78,11 +81,11 @@ defmodule ElixirScript.Translator.For do variables = Enum.filter(variables, fn(x) -> x != nil end) new_identifier = Enum.map(elements, fn(x) -> - case Translator.translate(x) do + case Translator.translate(x, env) do %ESTree.Identifier{} -> Builder.identifier(:undefined) _ -> - Translator.translate(x) + Translator.translate(x, env) end end) @@ -96,12 +99,12 @@ defmodule ElixirScript.Translator.For do Builder.for_of_statement( variable_declaration, - Translator.translate(enum), + Translator.translate(enum, env), Builder.block_statement( [ Builder.if_statement( - Utils.make_match(i, new_identifier), - Builder.block_statement(variables ++ List.wrap(handle_generators(tl(generators)))) + Utils.make_match(i, new_identifier, env), + Builder.block_statement(variables ++ List.wrap(handle_generators(tl(generators), env))) ) ] ) diff --git a/lib/elixir_script/translator/function.ex b/lib/elixir_script/translator/function.ex index 0dd8a5ce..56f9c202 100644 --- a/lib/elixir_script/translator/function.ex +++ b/lib/elixir_script/translator/function.ex @@ -1,6 +1,5 @@ defmodule ElixirScript.Translator.Function do @moduledoc false - require Logger alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator alias ElixirScript.Translator.Utils @@ -8,8 +7,8 @@ defmodule ElixirScript.Translator.Function do alias ElixirScript.Preprocess.Variables - def process_function(name, functions) do - result = make_anonymous_function(functions) + def process_function(name, functions, env) do + result = make_anonymous_function(functions, env) declarator = JS.variable_declarator( JS.identifier(name), @@ -19,34 +18,34 @@ defmodule ElixirScript.Translator.Function do JS.variable_declaration([declarator], :let) end - def make_anonymous_function(functions) do + def make_anonymous_function(functions, env \\ __ENV__) do clauses = functions |> Stream.map(fn(x) -> Variables.process(x) end) |> Stream.map(fn {:->, _, [ [{:when, _, [params | guards]}], body ]} -> { patterns, params } = Match.build_match(List.wrap(params)) params = make_params(params) - body = make_body(body) - guard_body = make_guards(guards) + body = make_body(body, env) + guard_body = make_guards(guards, env) do_make_function_clause(patterns, params, body, guard_body) ({:->, _, [params, body]}) -> { patterns, params } = Match.build_match(params) params = make_params(params) - body = make_body(body) + body = make_body(body, env) do_make_function_clause(patterns, params, body) ({_, _, [{:when, _, [{_, _, params} | guards] }, [do: body]]}) -> { patterns, params } = Match.build_match(params) params = make_params(params) - body = make_body(body) - guard_body = make_guards(guards) + body = make_body(body, env) + guard_body = make_guards(guards, env) do_make_function_clause(patterns, params, body, guard_body) ({_, _, [{_, _, params}, [do: body]]}) -> { patterns, params } = Match.build_match(params) params = make_params(params) - body = make_body(body) + body = make_body(body, env) do_make_function_clause(patterns, params, body) end) @@ -58,15 +57,15 @@ defmodule ElixirScript.Translator.Function do ) end - defp make_body(body) do + defp make_body(body, env) do body - |> prepare_function_body + |> prepare_function_body(env) |> JS.block_statement end - defp make_guards(guards) do + defp make_guards(guards, env) do hd(List.wrap(guards)) - |> prepare_function_body + |> prepare_function_body(env) |> JS.block_statement end @@ -104,7 +103,7 @@ defmodule ElixirScript.Translator.Function do ]) end - def make_function_or_property_call(module_name, function_name) do + def make_function_or_property_call(module_name, function_name, env) do the_name = case module_name do {:__aliases__, _, name} -> name @@ -131,16 +130,16 @@ defmodule ElixirScript.Translator.Function do ), [ Utils.make_module_expression_tree(the_name, false), - Translator.translate(to_string(function_name)) + Translator.translate(to_string(function_name), env) ] ) end - def make_function_call(function_name, params) do - Utils.make_call_expression(Utils.filter_name(function_name), params) + def make_function_call(function_name, params, env) do + Utils.make_call_expression(Utils.filter_name(function_name), params, env) end - def make_function_call(module_name, function_name, params) do + def make_function_call(module_name, function_name, params, env) do the_name = case module_name do {:__aliases__, _, name} -> name @@ -157,19 +156,19 @@ defmodule ElixirScript.Translator.Function do end end - Utils.make_call_expression(the_name, Utils.filter_name(function_name), params) + Utils.make_call_expression(the_name, Utils.filter_name(function_name), params, env) end - def prepare_function_body(body) do + def prepare_function_body(body, env) do case body do nil -> [] list when is_list(list) -> - Enum.map(list, &Translator.translate(&1)) + Enum.map(list, &Translator.translate(&1, env)) {:__block__, _, list} -> - Enum.map(list, &Translator.translate(&1)) + Enum.map(list, &Translator.translate(&1, env)) _ -> - [Translator.translate(body)] + [Translator.translate(body, env)] end |> Utils.inflate_groups |> return_last_expression diff --git a/lib/elixir_script/translator/kernel.ex b/lib/elixir_script/translator/kernel.ex index e61c296f..a5e1c29c 100644 --- a/lib/elixir_script/translator/kernel.ex +++ b/lib/elixir_script/translator/kernel.ex @@ -9,19 +9,6 @@ defmodule ElixirScript.Translator.Kernel do Translator.translate(quote do: Range.(unquote(first), unquote(last))) end - def make_unquote(expr) do - Builder.call_expression( - Builder.function_expression([],[], - Builder.block_statement([ - Builder.return_statement( - Translator.translate(expr) - ) - ]) - ), - [] - ) - end - def make___DIR__() do Utils.wrap_in_function_closure( diff --git a/lib/elixir_script/translator/module.ex b/lib/elixir_script/translator/module.ex index 3f72be12..81afa97b 100644 --- a/lib/elixir_script/translator/module.ex +++ b/lib/elixir_script/translator/module.ex @@ -23,21 +23,21 @@ defmodule ElixirScript.Translator.Module do {:fun, from: "__lib/funcy/fun" }, ] - def make_module(module_name_list, nil) do - [%JSModule{ name: module_name_list, body: List.wrap(create__module__(module_name_list)) }] + def make_module(module_name_list, nil, env) do + [%JSModule{ name: module_name_list, body: List.wrap(create__module__(module_name_list, env)) }] end - def make_module(module_name_list, body) do + def make_module(module_name_list, body, env) do body = make_inner_module_aliases(module_name_list, body) { body, aliases, used_stdlibs } = Aliases.process(body) { body, functions } = extract_functions_from_module(body) - { exported_functions, private_functions } = process_functions(functions) + { exported_functions, private_functions } = process_functions(functions, env) #Translate body - body = Translator.translate(body) + body = Translator.translate(body, env) body = case body do [%ESTree.BlockStatement{ body: body }] -> @@ -92,7 +92,7 @@ defmodule ElixirScript.Translator.Module do result = [ %JSModule{ name: module_name_list, - body: imports ++ List.wrap(create__module__(module_name_list)) ++ structs ++ private_functions ++ exported_functions ++ body ++ [default], + body: imports ++ List.wrap(create__module__(module_name_list, env)) ++ structs ++ private_functions ++ exported_functions ++ body ++ [default], stdlibs: used_stdlibs |> HashSet.to_list } ] ++ List.flatten(modules) @@ -226,33 +226,33 @@ defmodule ElixirScript.Translator.Module do end) end - defp process_functions(%{ exported: exported, private: private }) do + defp process_functions(%{ exported: exported, private: private }, env) do exported_functions = Enum.map(Dict.keys(exported), fn(key) -> functions = Dict.get(exported, key) - { key, Function.process_function(key, functions) } + { key, Function.process_function(key, functions, env) } end) private_functions = Enum.map(Dict.keys(private), fn(key) -> functions = Dict.get(private, key) - { key, Function.process_function(key, functions) } + { key, Function.process_function(key, functions, env) } end) { exported_functions, private_functions } end - def make_attribute(name, value) do + def make_attribute(name, value, env) do declarator = JS.variable_declarator( JS.identifier(name), - ElixirScript.Translator.translate(value) + ElixirScript.Translator.translate(value, env) ) JS.variable_declaration([declarator], :const) end - defp create__module__(module_name_list) do + defp create__module__(module_name_list, env) do declarator = JS.variable_declarator( JS.identifier(:__MODULE__), - ElixirScript.Translator.translate(List.last(module_name_list)) + ElixirScript.Translator.translate(List.last(module_name_list), env) ) JS.variable_declaration([declarator], :const) diff --git a/lib/elixir_script/translator/primitive.ex b/lib/elixir_script/translator/primitive.ex index 913ab648..212d3af3 100644 --- a/lib/elixir_script/translator/primitive.ex +++ b/lib/elixir_script/translator/primitive.ex @@ -34,13 +34,13 @@ defmodule ElixirScript.Translator.Primitive do ) end - def make_list(ast) when is_list(ast) do + def make_list(ast, env) when is_list(ast) do Builder.call_expression( Builder.member_expression( Builder.identifier("Erlang"), Builder.identifier("list") ), - Enum.map(ast, fn(x) -> Translator.translate(x) end) + Enum.map(ast, fn(x) -> Translator.translate(x, env) end) ) end @@ -64,17 +64,17 @@ defmodule ElixirScript.Translator.Primitive do ) end - def make_tuple({ one, two }) do - make_tuple([one, two]) + def make_tuple({ one, two }, env) do + make_tuple([one, two], env) end - def make_tuple(elements) do + def make_tuple(elements, env) do Builder.call_expression( Builder.member_expression( Builder.identifier("Erlang"), Builder.identifier("tuple") ), - Enum.map(elements, fn(x) -> Translator.translate(x) end) + Enum.map(elements, fn(x) -> Translator.translate(x, env) end) ) end diff --git a/lib/elixir_script/translator/receive.ex b/lib/elixir_script/translator/receive.ex index 9837b62c..e23c4779 100644 --- a/lib/elixir_script/translator/receive.ex +++ b/lib/elixir_script/translator/receive.ex @@ -1,10 +1,9 @@ defmodule ElixirScript.Translator.Receive do @moduledoc false - require Logger alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator - def make_receive([do: clauses]) do + def make_receive([do: clauses], env) do JS.call_expression( JS.member_expression( JS.member_expression( @@ -19,7 +18,7 @@ defmodule ElixirScript.Translator.Receive do [], JS.block_statement([ JS.return_statement( - ElixirScript.Translator.Case.make_case({:__aliases__, [], [:message]}, clauses) + ElixirScript.Translator.Case.make_case({:__aliases__, [], [:message]}, clauses, env) ) ]) ) @@ -27,7 +26,7 @@ defmodule ElixirScript.Translator.Receive do ) end - def make_receive([do: clauses, after: [{:->, _, [[time], _body]}] = after_clause]) do + def make_receive([do: clauses, after: [{:->, _, [[time], _body]}] = after_clause], env) do JS.call_expression( JS.member_expression( JS.member_expression( @@ -42,12 +41,12 @@ defmodule ElixirScript.Translator.Receive do [], JS.block_statement([ JS.return_statement( - ElixirScript.Translator.Case.make_case({:__aliases__, [], [:message]}, clauses) + ElixirScript.Translator.Case.make_case({:__aliases__, [], [:message]}, clauses, env) ) ]) ), - Translator.translate(time), - ElixirScript.Translator.Function.make_anonymous_function(after_clause) + Translator.translate(time, env), + ElixirScript.Translator.Function.make_anonymous_function(after_clause, env) ] ) end diff --git a/lib/elixir_script/translator/try.ex b/lib/elixir_script/translator/try.ex index 2e20ba13..f4e0d5a2 100644 --- a/lib/elixir_script/translator/try.ex +++ b/lib/elixir_script/translator/try.ex @@ -8,7 +8,7 @@ defmodule ElixirScript.Translator.Try do @error_identifier JS.identifier(:e) - def make_try(blocks) do + def make_try(blocks, env) do try_block = Dict.get(blocks, :do) rescue_block = Dict.get(blocks, :rescue) catch_block = Dict.get(blocks, :catch) @@ -21,7 +21,8 @@ defmodule ElixirScript.Translator.Try do processed_rescue_and_catch_blocks = process_rescue_and_catch( process_rescue_block(rescue_block), - process_catch_block(catch_block) + process_catch_block(catch_block), + env ) the_catch = case processed_rescue_and_catch_blocks do @@ -36,10 +37,10 @@ defmodule ElixirScript.Translator.Try do Utils.wrap_in_function_closure( JS.try_statement( Function.return_last_expression( - JS.block_statement(List.wrap(Translator.translate(try_block))) + JS.block_statement(List.wrap(Translator.translate(try_block, env))) ), the_catch, - Function.return_last_expression(process_after_block(after_block)) + Function.return_last_expression(process_after_block(after_block, env)) ) ) end @@ -70,13 +71,13 @@ defmodule ElixirScript.Translator.Try do |> List.flatten end - defp process_after_block(nil) do + defp process_after_block(nil, _) do nil end - defp process_after_block(after_block) do + defp process_after_block(after_block, env) do JS.block_statement(List.wrap( - Translator.translate(after_block) + Translator.translate(after_block, env) )) end @@ -88,14 +89,14 @@ defmodule ElixirScript.Translator.Try do catch_block end - def process_rescue_and_catch([], []) do + def process_rescue_and_catch([], [], _) do nil end - def process_rescue_and_catch(processed_rescue_block, processed_catch_block) do + def process_rescue_and_catch(processed_rescue_block, processed_catch_block, env) do processed_clauses = processed_catch_block ++ processed_rescue_block processed_clauses = processed_clauses ++ [{:->, [], [[], [quote do: throw(e)]]}] - Case.make_case({:e, [], __MODULE__}, processed_clauses) + Case.make_case({:e, [], __MODULE__}, processed_clauses, env) end defp convert_to_struct([module]) do diff --git a/lib/elixir_script/translator/utils.ex b/lib/elixir_script/translator/utils.ex index 744798f8..936aec9c 100644 --- a/lib/elixir_script/translator/utils.ex +++ b/lib/elixir_script/translator/utils.ex @@ -51,32 +51,32 @@ defmodule ElixirScript.Translator.Utils do Builder.identifier(module) end - def make_module_expression_tree(module, _computed) do - Translator.translate(module) + def make_module_expression_tree(module, _computed, env) do + Translator.translate(module, env) end - def make_call_expression_with_ast_params(module_name, function_name, params) do + def make_call_expression_with_ast_params(module_name, function_name, params, env) do Builder.call_expression( - make_member_expression(module_name, function_name), + make_member_expression(module_name, function_name, env), params ) end - def make_call_expression(module_name, function_name, params) do + def make_call_expression(module_name, function_name, params, env) do Builder.call_expression( - make_member_expression(module_name, function_name), - Enum.map(params, &Translator.translate(&1)) + make_member_expression(module_name, function_name, env), + Enum.map(params, &Translator.translate(&1, env)) ) end - def make_call_expression(function_name, params) do + def make_call_expression(function_name, params, env) do Builder.call_expression( Builder.identifier(function_name), - Enum.map(params, &Translator.translate(&1)) + Enum.map(params, &Translator.translate(&1, env)) ) end - def make_member_expression(module_name, function_name, computed \\ false) do + def make_member_expression(module_name, function_name, env, computed \\ false) do case module_name do modules when is_list(modules) and length(modules) > 1 -> ast = make_module_expression_tree(modules, computed) @@ -93,13 +93,13 @@ defmodule ElixirScript.Translator.Utils do ) {{:., _, [_module_name, _function_name]}, _, _params } = ast -> Builder.member_expression( - Translator.translate(ast), + Translator.translate(ast, env), build_function_name_ast(function_name), computed ) {:., _, _} = ast -> Builder.member_expression( - Translator.translate(ast), + Translator.translate(ast, env), build_function_name_ast(function_name), computed ) @@ -139,9 +139,9 @@ defmodule ElixirScript.Translator.Utils do ) end - def make_match(pattern, expr) do + def make_match(pattern, expr, env) do Builder.call_expression( - make_member_expression("Kernel", "match__qmark__"), + make_member_expression("Kernel", "match__qmark__", env), [ pattern, expr @@ -149,9 +149,9 @@ defmodule ElixirScript.Translator.Utils do ) end - def make_match(pattern, expr, guard) do + def make_match(pattern, expr, guard, env) do Builder.call_expression( - make_member_expression("Kernel", "match__qmark__"), + make_member_expression("Kernel", "match__qmark__", env), [ pattern, expr, diff --git a/mix.lock b/mix.lock index 8d77d4ab..d2e6dc25 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{"earmark": {:hex, :earmark, "0.1.17"}, "estree": {:hex, :estree, "2.0.1"}, - "ex_doc": {:hex, :ex_doc, "0.8.3"}, + "ex_doc": {:hex, :ex_doc, "0.8.4"}, "inflex": {:hex, :inflex, "1.4.1"}, "shouldi": {:hex, :shouldi, "0.2.3"}} diff --git a/test/test_helper.exs b/test/test_helper.exs index 14b6717a..bab1254a 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -2,11 +2,26 @@ exclude = if Node.alive?, do: [], else: [skip: true] ExUnit.start(exclude: exclude, formatters: [ShouldI.CLIFormatter]) +defmodule ElixirScript.Math do + defmacro squared(x) do + quote do + unquote(x) * unquote(x) + end + end +end + defmodule ElixirScript.TestHelper do use ShouldI - + + def make_custom_env do + require Logger + require ElixirScript.Math + __ENV__ + end + def ex_ast_to_js(ex_ast) do - ElixirScript.Translator.translate(ex_ast) + + ElixirScript.Translator.translate(ex_ast, make_custom_env) |> ElixirScript.javascript_ast_to_code end diff --git a/test/translator/assignment_test.exs b/test/translator/assignment_test.exs index a236d4d1..b4849bac 100644 --- a/test/translator/assignment_test.exs +++ b/test/translator/assignment_test.exs @@ -15,7 +15,9 @@ defmodule ElixirScript.Translator.Assignment.Test do end should "translate tuple assignment" do - ex_ast = quote do: {a, b} = {1, 2} + ex_ast = quote do + {a, b} = {1, 2} + end js_code = """ let [a, b] = fun.bind(Erlang.tuple(fun.parameter, fun.parameter), Erlang.tuple(1, 2)); let _ref = Erlang.tuple(a, b); diff --git a/test/translator/function_test.exs b/test/translator/function_test.exs index effabfe0..4fff7f75 100644 --- a/test/translator/function_test.exs +++ b/test/translator/function_test.exs @@ -2,6 +2,22 @@ defmodule ElixirScript.Translator.Function.Test do use ShouldI import ElixirScript.TestHelper + should "translate function with a macro" do + ex_ast = quote do + def test1() do + ElixirScript.Math.squared(1) + end + end + + js_code = """ + let test1 = fun([[], function() { + return 1 * 1; + }]); + """ + + assert_translation(ex_ast, js_code) + end + should "translate functions" do ex_ast = quote do def test1() do From 1c768bb078118216d3182973dea56412c70fb373 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Sun, 30 Aug 2015 21:54:19 -0500 Subject: [PATCH 02/15] Added logger translator. Moved Kernel translations to kernel.ex --- lib/elixir_script/translator.ex | 109 ++++++++------------- lib/elixir_script/translator/assignment.ex | 1 - lib/elixir_script/translator/import.ex | 30 +++--- lib/elixir_script/translator/kernel.ex | 87 +++++++++++++--- lib/elixir_script/translator/logger.ex | 24 +++++ lib/elixir_script/translator/utils.ex | 58 +++++------ test/translator/case_test.exs | 12 +-- test/translator/list_test.exs | 4 +- test/translator/string_test.exs | 2 +- 9 files changed, 190 insertions(+), 137 deletions(-) create mode 100644 lib/elixir_script/translator/logger.ex diff --git a/lib/elixir_script/translator.ex b/lib/elixir_script/translator.ex index ce1db316..02f7702b 100644 --- a/lib/elixir_script/translator.ex +++ b/lib/elixir_script/translator.ex @@ -6,23 +6,24 @@ defmodule ElixirScript.Translator do alias ElixirScript.Translator.Assignment alias ElixirScript.Translator.Map alias ElixirScript.Translator.Struct - alias ElixirScript.Translator.Raise alias ElixirScript.Translator.Function alias ElixirScript.Translator.Capture - alias ElixirScript.Translator.Expression alias ElixirScript.Translator.Import - alias ElixirScript.Translator.If alias ElixirScript.Translator.Cond alias ElixirScript.Translator.Case alias ElixirScript.Translator.For alias ElixirScript.Translator.Try alias ElixirScript.Translator.Block + alias ElixirScript.Translator.Struct alias ElixirScript.Translator.Module alias ElixirScript.Translator.Utils alias ElixirScript.Translator.Bitstring alias ElixirScript.Translator.Receive alias ElixirScript.Translator.Quote + alias ElixirScript.Translator.Utils + alias ElixirScript.Translator.Logger alias ElixirScript.Translator.Kernel, as: ExKernel + alias ESTree.Tools.Builder, as: JS @doc """ Translates Elixir AST to JavaScript AST @@ -110,12 +111,17 @@ defmodule ElixirScript.Translator do end end + defp do_translate({{:., context, [{:__aliases__, _, [:Logger]}, function_name]}, _, params }, env) do + Logger.make_logger(function_name, params, env) + end + defp do_translate({{:., _, [Access, :get]}, _, [target, property]}, env) do Map.make_get_property(target, property) end defp do_translate({:., _, [module_name, function_name]} = ast, env) do expanded_ast = Macro.expand(ast, env) + if expanded_ast == ast do Function.make_function_or_property_call(module_name, function_name, env) else @@ -125,6 +131,7 @@ defmodule ElixirScript.Translator do defp do_translate({{:., _, [module_name, function_name]}, _, [] } = ast, env) do expanded_ast = Macro.expand(ast, env) + if expanded_ast == ast do Function.make_function_or_property_call(module_name, function_name, env) else @@ -142,11 +149,15 @@ defmodule ElixirScript.Translator do end defp do_translate({{:., _, [module_name, function_name]}, _, params } = ast, env) do - expanded_ast = Macro.expand(ast, env) - if expanded_ast == ast do - Function.make_function_call(module_name, function_name, params, env) + if module_name == Kernel do + ExKernel.translate_kernel_function(function_name, params, env) else - translate(expanded_ast, env) + expanded_ast = Macro.expand(ast, env) + if expanded_ast == ast do + Function.make_function_call(module_name, function_name, params, env) + else + translate(expanded_ast, env) + end end end @@ -163,7 +174,17 @@ defmodule ElixirScript.Translator do end defp do_translate({:__DIR__, _, _expressions }, env) do - ExKernel.make___DIR__() + Utils.wrap_in_function_closure( + JS.if_statement( + JS.identifier(:__dirname), + JS.block_statement([ + JS.return_statement(JS.identifier(:__dirname)) + ]), + JS.block_statement([ + JS.return_statement(JS.literal(nil)) + ]) + ) + ) end defp do_translate({:try, _, [ blocks ]}, env) do @@ -202,11 +223,11 @@ defmodule ElixirScript.Translator do Import.make_import(module_name_list, options) end - defp do_translate({:alias, _, [alias_info, options]}, env) do + defp do_translate({:alias, _, [alias_info, options]}, env) when is_tuple(alias_info) do Import.make_alias_import(alias_info, options) end - defp do_translate({:alias, _, [alias_info]}, env) do + defp do_translate({:alias, _, [alias_info]}, env) when is_tuple(alias_info) do Import.make_alias_import(alias_info, []) end @@ -242,34 +263,10 @@ defmodule ElixirScript.Translator do Primitive.make_tuple(elements, env) end - defp do_translate({operator, _, [value]}, env) when operator in [:-, :!] do - Expression.make_unary_expression(operator, value, env) - end - defp do_translate({:=, _, [left, right]}, env) do Assignment.make_assignment(left, right, env) end - defp do_translate({:<>, _, [left, right]}, env) do - Expression.make_binary_expression(:+, left, right, env) - end - - defp do_translate({:++, _, [left, right]}, env) do - ExKernel.concat_lists(left, right) - end - - defp do_translate({operator, _, [left, right]}, env) when operator in [:+, :-, :/, :*, :==, :!=, :&&, :||, :>, :<, :>=, :<=, :===] do - Expression.make_binary_expression(operator, left, right, env) - end - - defp do_translate({:and, _, [left, right]}, env) do - Expression.make_binary_expression(:&&, left, right, env) - end - - defp do_translate({:or, _, [left, right]}, env) do - Expression.make_binary_expression(:||, left, right, env) - end - defp do_translate({function, _, [{:when, _, [{name, _, _params} | _guards] }, [do: _body]] } = ast, env) when function in [:def, :defp] do Function.process_function(Utils.filter_name(name), [ast], env) end @@ -286,47 +283,21 @@ defmodule ElixirScript.Translator do Struct.make_defexception(attributes) end - defp do_translate({:raise, _, [alias_info, attributes]}, env) do - {_, _, name} = alias_info - - Raise.throw_error(name, attributes) - end - - defp do_translate({:raise, _, [message]}, env) do - Raise.throw_error(message) - end - - defp do_translate({:if, _, _} = ast, env) do - Macro.expand(ast, env) - |> translate - end - defp do_translate({:defmodule, _, [{:__aliases__, _, module_name_list}, [do: body]]}, env) do Module.make_module(module_name_list, body, env) end - defp do_translate({:|>, _, [left, right]}, env) do - case right do - {{:., meta, [module, fun]}, meta2, params} -> - translate({{:., meta, [module, fun]}, meta2, [left] ++ params}) - {fun, meta, params} -> - translate({fun, meta, [left] ++ params}) - end - end - defp do_translate({name, metadata, params} = ast, env) when is_list(params) do - expanded_ast = Macro.expand(ast, env) - if expanded_ast == ast do - name = Utils.filter_name(name) - - case metadata[:import] do - Kernel -> - Function.make_function_call(:Kernel, name, params, env) - _ -> - Function.make_function_call(name, params, env) - end + if metadata[:import] == Kernel do + ExKernel.translate_kernel_function(name, params, env) else - translate(expanded_ast) + expanded_ast = Macro.expand(ast, env) + if expanded_ast == ast do + name = Utils.filter_name(name) + Function.make_function_call(name, params, env) + else + translate(expanded_ast) + end end end diff --git a/lib/elixir_script/translator/assignment.ex b/lib/elixir_script/translator/assignment.ex index 4787b9e2..09b0f8d8 100644 --- a/lib/elixir_script/translator/assignment.ex +++ b/lib/elixir_script/translator/assignment.ex @@ -1,6 +1,5 @@ defmodule ElixirScript.Translator.Assignment do @moduledoc false - require Logger alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator alias ElixirScript.Translator.PatternMatching.Match diff --git a/lib/elixir_script/translator/import.ex b/lib/elixir_script/translator/import.ex index 23aaf9a9..c3d41d2b 100644 --- a/lib/elixir_script/translator/import.ex +++ b/lib/elixir_script/translator/import.ex @@ -1,7 +1,7 @@ defmodule ElixirScript.Translator.Import do @moduledoc false require Logger - alias ESTree.Tools.Builder + alias ESTree.Tools.Builder, as: JS def make_alias_import(alias_info, options) do {_, _, name} = alias_info @@ -9,14 +9,14 @@ defmodule ElixirScript.Translator.Import do import_specifier = if options[:as] do {_, _, alt} = options[:as] - Builder.import_specifier( - Builder.identifier("default"), - Builder.identifier(List.last(alt)) + JS.import_specifier( + JS.identifier("default"), + JS.identifier(List.last(alt)) ) else - Builder.import_default_specifier( - Builder.identifier(List.last(name)), - Builder.identifier(List.last(name)) + JS.import_default_specifier( + JS.identifier(List.last(name)), + JS.identifier(List.last(name)) ) end @@ -26,24 +26,24 @@ defmodule ElixirScript.Translator.Import do make_source(name) end - Builder.import_declaration( + JS.import_declaration( [import_specifier], - Builder.identifier(import_path) + JS.identifier(import_path) ) end def make_import(module_name_list, options) do - mod = List.last(module_name_list) |> Builder.identifier + mod = List.last(module_name_list) |> JS.identifier specifiers = if options[:only] do Enum.map(options[:only], fn({name, _arity}) -> - Builder.import_specifier( - Builder.identifier(name), - Builder.identifier(name) + JS.import_specifier( + JS.identifier(name), + JS.identifier(name) ) end) else - List.wrap(Builder.import_namespace_specifier(mod)) + List.wrap(JS.import_namespace_specifier(mod)) end import_path = if options[:from] do @@ -52,7 +52,7 @@ defmodule ElixirScript.Translator.Import do make_source(module_name_list) end - Builder.import_declaration(specifiers, Builder.identifier(import_path)) + JS.import_declaration(specifiers, JS.identifier(import_path)) end defp make_source(name) do diff --git a/lib/elixir_script/translator/kernel.ex b/lib/elixir_script/translator/kernel.ex index a5e1c29c..e573ecc7 100644 --- a/lib/elixir_script/translator/kernel.ex +++ b/lib/elixir_script/translator/kernel.ex @@ -1,29 +1,88 @@ defmodule ElixirScript.Translator.Kernel do @moduledoc false require Logger - alias ESTree.Tools.Builder + alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator alias ElixirScript.Utils + alias ElixirScript.Translator.Function + alias ElixirScript.Translator.Expression + alias ElixirScript.Translator.If + alias ElixirScript.Translator.Raise + alias ElixirScript.Translator.Module + alias ElixirScript.Translator.Struct + alias ElixirScript.Translator.Utils - def make_range(first, last) do - Translator.translate(quote do: Range.(unquote(first), unquote(last))) + def translate_kernel_function(name, params, env) do + do_translate({name, [], params}, env) + end + + defp do_translate({operator, _, [value]}, env) when operator in [:-, :!] do + Expression.make_unary_expression(operator, value, env) end + defp do_translate({:<>, _, [left, right]}, env) do + Expression.make_binary_expression(:+, left, right, env) + end - def make___DIR__() do - Utils.wrap_in_function_closure( - Builder.if_statement( - Builder.identifier(:__dirname), - Builder.block_statement([ - Builder.return_statement(Builder.identifier(:__dirname)) - ]), - Builder.block_statement([ - Builder.return_statement(Builder.literal(nil)) - ]) - ) + defp do_translate({:++, _, [left, right]}, env) do + JS.call_expression( + JS.member_expression( + Translator.translate(left, env), + JS.identifier(:concat) + ), + [ + Translator.translate(right, env), + ] ) end + defp do_translate({operator, _, [left, right]}, env) when operator in [:+, :-, :/, :*, :==, :!=, :&&, :||, :>, :<, :>=, :<=, :===] do + Expression.make_binary_expression(operator, left, right, env) + end + + defp do_translate({:and, _, [left, right]}, env) do + Expression.make_binary_expression(:&&, left, right, env) + end + + defp do_translate({:or, _, [left, right]}, env) do + Expression.make_binary_expression(:||, left, right, env) + end + + defp do_translate({:if, _, [test, blocks]}, env) do + If.make_if(test, blocks) + end + + defp do_translate({:|>, _, [left, right]}, env) do + case right do + {{:., meta, [module, fun]}, meta2, params} -> + Translator.translate({{:., meta, [module, fun]}, meta2, [left] ++ params}) + {fun, meta, params} -> + Translator.translate({fun, meta, [left] ++ params}) + end + end + + defp do_translate({:raise, _, [alias_info, attributes]}, env) do + {_, _, name} = alias_info + + Raise.throw_error(name, attributes) + end + + defp do_translate({:raise, _, [message]}, env) do + Raise.throw_error(message) + end + + defp do_translate({:to_string, _, [param]}, env) when is_binary(param) do + Translator.translate(param) + end + + defp do_translate({name, _, params}, env) do + Function.make_function_call(:Kernel, name, params, env) + end + + def make_range(first, last) do + Translator.translate(quote do: Range.(unquote(first), unquote(last))) + end + def concat_lists(list1, list2) do Builder.call_expression( Builder.member_expression( diff --git a/lib/elixir_script/translator/logger.ex b/lib/elixir_script/translator/logger.ex new file mode 100644 index 00000000..17ca5e75 --- /dev/null +++ b/lib/elixir_script/translator/logger.ex @@ -0,0 +1,24 @@ +defmodule ElixirScript.Translator.Logger do + @moduledoc false + alias ESTree.Tools.Builder, as: JS + alias ElixirScript.Translator + + + def make_logger(:log, params, env) do + do_make_logger(hd(params), tl(params), env) + end + + def make_logger(level, params, env) do + do_make_logger(level, params, env) + end + + defp do_make_logger(level, params, env) do + JS.call_expression( + JS.member_expression( + JS.identifier("console"), + JS.identifier(level) + ), + Enum.map(params, fn(x) -> Translator.translate(x, env) end) + ) + end +end \ No newline at end of file diff --git a/lib/elixir_script/translator/utils.ex b/lib/elixir_script/translator/utils.ex index 936aec9c..975bbb90 100644 --- a/lib/elixir_script/translator/utils.ex +++ b/lib/elixir_script/translator/utils.ex @@ -1,6 +1,6 @@ defmodule ElixirScript.Translator.Utils do @moduledoc false - alias ESTree.Tools.Builder + alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator def inflate_groups(body) do @@ -20,11 +20,11 @@ defmodule ElixirScript.Translator.Utils do end def make_throw_statement(error_name, message) do - Builder.throw_statement( - Builder.new_expression( - Builder.identifier(error_name), + JS.throw_statement( + JS.new_expression( + JS.identifier(error_name), [ - Builder.literal(message) + JS.literal(message) ] ) ) @@ -38,17 +38,17 @@ defmodule ElixirScript.Translator.Utils do Enum.reduce(modules, nil, fn(x, ast) -> case ast do nil -> - Builder.member_expression(Builder.identifier(x), nil, computed) + JS.member_expression(JS.identifier(x), nil, computed) %ESTree.MemberExpression{ property: nil } -> - %{ ast | property: Builder.identifier(x) } + %{ ast | property: JS.identifier(x) } _ -> - Builder.member_expression(ast, Builder.identifier(x), computed) + JS.member_expression(ast, JS.identifier(x), computed) end end) end def make_module_expression_tree(module, _computed) when is_binary(module) or is_atom(module) do - Builder.identifier(module) + JS.identifier(module) end def make_module_expression_tree(module, _computed, env) do @@ -56,22 +56,22 @@ defmodule ElixirScript.Translator.Utils do end def make_call_expression_with_ast_params(module_name, function_name, params, env) do - Builder.call_expression( + JS.call_expression( make_member_expression(module_name, function_name, env), params ) end def make_call_expression(module_name, function_name, params, env) do - Builder.call_expression( + JS.call_expression( make_member_expression(module_name, function_name, env), Enum.map(params, &Translator.translate(&1, env)) ) end def make_call_expression(function_name, params, env) do - Builder.call_expression( - Builder.identifier(function_name), + JS.call_expression( + JS.identifier(function_name), Enum.map(params, &Translator.translate(&1, env)) ) end @@ -80,32 +80,32 @@ defmodule ElixirScript.Translator.Utils do case module_name do modules when is_list(modules) and length(modules) > 1 -> ast = make_module_expression_tree(modules, computed) - Builder.member_expression( + JS.member_expression( ast, build_function_name_ast(function_name), computed ) modules when is_list(modules) and length(modules) == 1 -> - Builder.member_expression( - Builder.identifier(hd(modules)), + JS.member_expression( + JS.identifier(hd(modules)), build_function_name_ast(function_name), computed ) {{:., _, [_module_name, _function_name]}, _, _params } = ast -> - Builder.member_expression( + JS.member_expression( Translator.translate(ast, env), build_function_name_ast(function_name), computed ) {:., _, _} = ast -> - Builder.member_expression( + JS.member_expression( Translator.translate(ast, env), build_function_name_ast(function_name), computed ) _ -> - Builder.member_expression( - Builder.identifier(module_name), + JS.member_expression( + JS.identifier(module_name), build_function_name_ast(function_name), computed ) @@ -113,7 +113,7 @@ defmodule ElixirScript.Translator.Utils do end def build_function_name_ast(function_name) do - Builder.identifier(function_name) + JS.identifier(function_name) end def make_array_accessor_call(name, index) do @@ -128,19 +128,19 @@ defmodule ElixirScript.Translator.Utils do [body] end - Builder.call_expression( - Builder.member_expression( - Builder.function_expression([],[], - Builder.block_statement(the_body) + JS.call_expression( + JS.member_expression( + JS.function_expression([],[], + JS.block_statement(the_body) ), - Builder.identifier("call") + JS.identifier("call") ), - [Builder.identifier("this")] + [JS.identifier("this")] ) end def make_match(pattern, expr, env) do - Builder.call_expression( + JS.call_expression( make_member_expression("Kernel", "match__qmark__", env), [ pattern, @@ -150,7 +150,7 @@ defmodule ElixirScript.Translator.Utils do end def make_match(pattern, expr, guard, env) do - Builder.call_expression( + JS.call_expression( make_member_expression("Kernel", "match__qmark__", env), [ pattern, diff --git a/test/translator/case_test.exs b/test/translator/case_test.exs index 41e53f58..e1fb1278 100644 --- a/test/translator/case_test.exs +++ b/test/translator/case_test.exs @@ -107,7 +107,7 @@ defmodule ElixirScript.Translator.Case.Test do js_code = """ fun([[Erlang.atom('ok')], function() { - Logger.info('info'); + console.info('info'); return Todo.add(data); }], [[Erlang.atom('error')], function() { return null; @@ -129,7 +129,7 @@ defmodule ElixirScript.Translator.Case.Test do js_code = """ fun([[Erlang.tuple(fun.parameter, fun.parameter)], function(one, two) { - return Logger.info(one); + return console.info(one); }], [[Erlang.atom('error')], function() { return null; }]).call(data) @@ -153,7 +153,7 @@ defmodule ElixirScript.Translator.Case.Test do [ [Erlang.tuple(Erlang.tuple(fun.parameter, fun.parameter), fun.parameter)], function(one, two, three) { - return Logger.info(one); + return console.info(one); } ], [ @@ -178,7 +178,7 @@ defmodule ElixirScript.Translator.Case.Test do js_code = """ fun([[Erlang.tuple(fun.parameter, Erlang.tuple(fun.parameter, fun.parameter))], function(one, two, three) { - return Logger.info(one); + return console.info(one); }], [[Erlang.atom('error')], function() { return null; }]).call(data) @@ -201,7 +201,7 @@ defmodule ElixirScript.Translator.Case.Test do [ [{'__struct__': Erlang.atom('AStruct'), 'key': {'__struct__': Erlang.atom('BStruct'), 'key2': fun.parameter}}], function(value){ - return Logger.info(value); + return console.info(value); } ], [ @@ -230,7 +230,7 @@ defmodule ElixirScript.Translator.Case.Test do [ [{'__struct__': Erlang.atom('AStruct'), 'key': {'__struct__': Erlang.atom('BStruct'), 'key2': fun.parameter, 'key3': {'__struct__': Erlang.atom('CStruct'), 'key4': fun.parameter}}}], function(value,value2){ - return Logger.info(value); + return console.info(value); } ], [ diff --git a/test/translator/list_test.exs b/test/translator/list_test.exs index 275f7e8f..0360dcaf 100644 --- a/test/translator/list_test.exs +++ b/test/translator/list_test.exs @@ -26,12 +26,12 @@ defmodule ElixirScript.Translator.List.Test do should "concatenate lists" do ex_ast = quote do: [1, 2, 3] ++ [4, 5, 6] - js_code = "List.concat(Erlang.list(1,2,3),Erlang.list(4,5,6))" + js_code = "Erlang.list(1,2,3).concat(Erlang.list(4,5,6))" assert_translation(ex_ast, js_code) ex_ast = quote do: this.list ++ [4, 5, 6] - js_code = "List.concat(Kernel.JS.get_property_or_call_function(this,'list'),Erlang.list(4,5,6))" + js_code = "Kernel.JS.get_property_or_call_function(this,'list').concat(Erlang.list(4,5,6))" assert_translation(ex_ast, js_code) end diff --git a/test/translator/string_test.exs b/test/translator/string_test.exs index bb906edf..4f17b92c 100644 --- a/test/translator/string_test.exs +++ b/test/translator/string_test.exs @@ -22,7 +22,7 @@ defmodule ElixirScript.Translator.String.Test do should "translate string interpolation" do ex_ast = quote do: "Hello #{"world"}" - assert_translation(ex_ast, "'Hello ' + Kernel.to_string('world')") + assert_translation(ex_ast, "'Hello ' + 'world'") ex_ast = quote do: "Hello #{length([])}" From 81a0a5341d1611d4dae6374605bcc3f81b5ae7c7 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Sun, 30 Aug 2015 21:57:57 -0500 Subject: [PATCH 03/15] Added env option to ElixirScript.transpile --- lib/elixir_script.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/elixir_script.ex b/lib/elixir_script.ex index ba3516c6..7cff1b3a 100644 --- a/lib/elixir_script.ex +++ b/lib/elixir_script.ex @@ -19,6 +19,8 @@ defmodule ElixirScript do 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 """ @doc """ @@ -38,8 +40,9 @@ defmodule ElixirScript do def transpile_quoted(quoted, opts \\ []) do include_path = Dict.get(opts, :include_path, false) root = Dict.get(opts, :root) + env = Dict.get(opts, :env, __ENV__) - case Translator.translate(quoted) do + case Translator.translate(quoted, env) do modules when is_list(modules) -> List.flatten(modules) |> Enum.map(fn(x) -> From 0ef830ba051739b522c5760ef9a227e61107a71b Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Sun, 30 Aug 2015 21:58:54 -0500 Subject: [PATCH 04/15] Added env option to ElixirScript.transpile_path --- lib/elixir_script.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/elixir_script.ex b/lib/elixir_script.ex index 7cff1b3a..d2f816f8 100644 --- a/lib/elixir_script.ex +++ b/lib/elixir_script.ex @@ -62,13 +62,14 @@ defmodule ElixirScript do def transpile_path(path, opts \\ []) do include_path = Dict.get(opts, :include_path, false) root = Dict.get(opts, :root) + env = Dict.get(opts, :env, __ENV__) path |> Path.wildcard |> Enum.map(fn(x) -> File.read!(x) |> Code.string_to_quoted! - |> Translator.translate + |> Translator.translate(env) end) |> List.flatten |> Enum.map(fn(x) -> From 649dc4a383a334877bdd77f93b16ef970b000ccc Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Mon, 31 Aug 2015 14:54:48 -0500 Subject: [PATCH 05/15] Updated to include test for calling ElixirScript.transpile with custom env for macros --- lib/elixir_script/preprocess/aliases.ex | 41 +++++++++++------ lib/elixir_script/translator.ex | 60 ++++++++++++------------- lib/elixir_script/translator/module.ex | 3 +- test/elixir_script_test.exs | 6 ++- 4 files changed, 62 insertions(+), 48 deletions(-) diff --git a/lib/elixir_script/preprocess/aliases.ex b/lib/elixir_script/preprocess/aliases.ex index a4cc4544..c54b4409 100644 --- a/lib/elixir_script/preprocess/aliases.ex +++ b/lib/elixir_script/preprocess/aliases.ex @@ -29,7 +29,7 @@ defmodule ElixirScript.Preprocess.Aliases do #the "Hello.World" would be placed in a set and later used for building an alias """ - def process(ast) do + def process(ast, env) do #These will always be added stdlib = HashSet.new @@ -42,36 +42,49 @@ defmodule ElixirScript.Preprocess.Aliases do state = %{ add: HashSet.new, defined: HashSet.new, stdlib: stdlib } {new_ast, state } = Macro.prewalk(ast, state, fn(x, acc) -> - process_aliases(x, acc) + process_aliases(x, acc, env) end) { new_ast, state.add, state.stdlib } end - def process_aliases({:alias, _, [{:__aliases__, _, _name}, [as: {:__aliases__, _, alias_name}]]} = ast, state) do + def process_aliases({:alias, _, [{:__aliases__, _, _name}, [as: {:__aliases__, _, alias_name}]]} = ast, state, _) do { ast, %{state | defined: HashSet.put(state.defined, List.last(alias_name)) } } end - def process_aliases({:alias, _, [{:__aliases__, _, name}]} = ast, state) do + def process_aliases({:alias, _, [{:__aliases__, _, name}]} = ast, state, _) do { ast, %{state | defined: HashSet.put(state.defined, List.last(name)) } } end - def process_aliases({{:., _, [{:__aliases__, _, aliases}, _]}, _, _} = ast, state) when aliases in @standard_libs do - {ast, %{ state | stdlib: HashSet.put(state.stdlib, List.last(aliases)) }} - end - def process_aliases({{:., meta1, [{:__aliases__, meta2, aliases}, function]}, meta3, params}, state) do - if HashSet.member?(state.defined, List.last(aliases)) do - { {{:., meta1, [{:__aliases__, meta2, List.last(aliases) |> List.wrap }, function]}, meta3, params}, state } + def process_aliases({{:., _, [{:__aliases__, _, aliases}, _]}, _, _} = ast, state, env) when aliases in @standard_libs do + expanded_ast = Macro.expand(ast, env) + if expanded_ast == ast do + {ast, %{ state | stdlib: HashSet.put(state.stdlib, List.last(aliases)) }} else - { - {{:., meta1, [{:__aliases__, meta2, List.last(aliases) |> List.wrap }, function]}, meta3, params}, + process_aliases(expanded_ast, state, env) + end + end + + def process_aliases({{:., meta1, [{:__aliases__, meta2, aliases}, function]}, meta3, params} = ast, state, env) do + expanded_ast = Macro.expand(ast, env) + + if expanded_ast == ast do + new_ast = {{:., meta1, [{:__aliases__, meta2, List.last(aliases) |> List.wrap }, function]}, meta3, params} + + new_state = if HashSet.member?(state.defined, List.last(aliases)) do + state + else %{ state | add: HashSet.put(state.add, aliases) } - } + end + + { new_ast, new_state } + else + process_aliases(expanded_ast, state, env) end end - def process_aliases(ast, state) do + def process_aliases(ast, state, env) do {ast, state} end end \ No newline at end of file diff --git a/lib/elixir_script/translator.ex b/lib/elixir_script/translator.ex index 02f7702b..c3231e22 100644 --- a/lib/elixir_script/translator.ex +++ b/lib/elixir_script/translator.ex @@ -32,11 +32,11 @@ defmodule ElixirScript.Translator do do_translate(ast, env) end - defp do_translate(ast, env) when is_number(ast) or is_binary(ast) or is_boolean(ast) or is_nil(ast) do + defp do_translate(ast, _) when is_number(ast) or is_binary(ast) or is_boolean(ast) or is_nil(ast) do Primitive.make_literal(ast) end - defp do_translate(ast, env) when is_atom(ast) do + defp do_translate(ast, _) when is_atom(ast) do Primitive.make_atom(ast) end @@ -48,7 +48,7 @@ defmodule ElixirScript.Translator do Primitive.make_tuple({one, two}, env) end - defp do_translate({:&, [], [number]}, env) when is_number(number) do + defp do_translate({:&, [], [number]}, _) when is_number(number) do Primitive.make_identifier(String.to_atom("__#{number}")) end @@ -72,26 +72,26 @@ defmodule ElixirScript.Translator do Module.make_attribute(name, value, env) end - defp do_translate({:@, _, [{name, _, _}]}, env) do + defp do_translate({:@, _, [{name, _, _}]}, _) do name = Utils.filter_name(name) Primitive.make_identifier(name) end - defp do_translate({:%, _, [alias_info, data]}, env) do + defp do_translate({:%, _, [alias_info, data]}, _) do {_, _, name} = alias_info {_, _, data} = data Struct.make_struct(name, data) end - defp do_translate({:%{}, _, [{:|, _, [map, data]}]}, env) do + defp do_translate({:%{}, _, [{:|, _, [map, data]}]}, _) do Map.make_map_update(map, data); end - defp do_translate({:%{}, _, properties}, env) do + defp do_translate({:%{}, _, properties}, _) do Map.make_object(properties) end - defp do_translate({:<<>>, _, elements}, env) do + defp do_translate({:<<>>, _, elements}, _) do is_interpolated_string = Enum.all?(elements, fn(x) -> case x do b when is_binary(b) -> @@ -111,11 +111,11 @@ defmodule ElixirScript.Translator do end end - defp do_translate({{:., context, [{:__aliases__, _, [:Logger]}, function_name]}, _, params }, env) do + defp do_translate({{:., _, [{:__aliases__, _, [:Logger]}, function_name]}, _, params }, env) do Logger.make_logger(function_name, params, env) end - defp do_translate({{:., _, [Access, :get]}, _, [target, property]}, env) do + defp do_translate({{:., _, [Access, :get]}, _, [target, property]}, _) do Map.make_get_property(target, property) end @@ -161,19 +161,19 @@ defmodule ElixirScript.Translator do end end - defp do_translate({:_, _, _}, env) do + defp do_translate({:_, _, _}, _env) do Primitive.make_identifier(:undefined) end - defp do_translate({:__aliases__, _, aliases}, env) do + defp do_translate({:__aliases__, _, aliases}, _) do Primitive.make_identifier(aliases) end - defp do_translate({:__block__, _, expressions }, env) do + defp do_translate({:__block__, _, expressions }, _) do Block.make_block(expressions) end - defp do_translate({:__DIR__, _, _expressions }, env) do + defp do_translate({:__DIR__, _, _expressions }, _) do Utils.wrap_in_function_closure( JS.if_statement( JS.identifier(:__dirname), @@ -195,47 +195,47 @@ defmodule ElixirScript.Translator do Receive.make_receive(expressions, env); end - defp do_translate({:super, _, _expressions }, env) do + defp do_translate({:super, _, _expressions }, _) do raise ElixirScript.UnsupportedError, :super end - defp do_translate({:__CALLER__, _, _expressions }, env) do + defp do_translate({:__CALLER__, _, _expressions }, _) do raise ElixirScript.UnsupportedError, :__CALLER__ end - defp do_translate({:__ENV__, _, _expressions }, env) do + defp do_translate({:__ENV__, _, _expressions }, _) do raise ElixirScript.UnsupportedError, :__ENV__ end - defp do_translate({:quote, _, [[do: expr]]}, env) do + defp do_translate({:quote, _, [[do: expr]]}, _) do Quote.make_quote([], expr) end - defp do_translate({:quote, _, [opts, [do: expr]]}, env) do + defp do_translate({:quote, _, [opts, [do: expr]]}, _) do Quote.make_quote(opts, expr) end - defp do_translate({:import, _, [{:__aliases__, _, module_name_list}]}, env) do + defp do_translate({:import, _, [{:__aliases__, _, module_name_list}]}, _) do Import.make_import(module_name_list, []) end - defp do_translate({:import, _, [{:__aliases__, _, module_name_list}, options ]}, env) do + defp do_translate({:import, _, [{:__aliases__, _, module_name_list}, options ]}, _) do Import.make_import(module_name_list, options) end - defp do_translate({:alias, _, [alias_info, options]}, env) when is_tuple(alias_info) do + defp do_translate({:alias, _, [alias_info, options]}, _) when is_tuple(alias_info) do Import.make_alias_import(alias_info, options) end - defp do_translate({:alias, _, [alias_info]}, env) when is_tuple(alias_info) do + defp do_translate({:alias, _, [alias_info]}, _) when is_tuple(alias_info) do Import.make_alias_import(alias_info, []) end - defp do_translate({:require, _, [alias_info, options]}, env) do + defp do_translate({:require, _, [alias_info, options]}, _) do Import.make_alias_import(alias_info, options) end - defp do_translate({:require, _, [alias_info]}, env) do + defp do_translate({:require, _, [alias_info]}, _) do Import.make_alias_import(alias_info, []) end @@ -243,7 +243,7 @@ defmodule ElixirScript.Translator do Case.make_case(condition, clauses, env) end - defp do_translate({:cond, _, [[do: clauses]]}, env) do + defp do_translate({:cond, _, [[do: clauses]]}, _) do Cond.make_cond(clauses) end @@ -255,7 +255,7 @@ defmodule ElixirScript.Translator do Function.make_anonymous_function(clauses, env) end - defp do_translate({:.., _, [first, last]}, env) do + defp do_translate({:.., _, [first, last]}, _) do ExKernel.make_range(first, last) end @@ -275,11 +275,11 @@ defmodule ElixirScript.Translator do Function.process_function(Utils.filter_name(name), [ast], env) end - defp do_translate({:defstruct, _, attributes}, env) do + defp do_translate({:defstruct, _, attributes}, _) do Struct.make_defstruct(attributes) end - defp do_translate({:defexception, _, attributes}, env) do + defp do_translate({:defexception, _, attributes}, _) do Struct.make_defexception(attributes) end @@ -301,7 +301,7 @@ defmodule ElixirScript.Translator do end end - defp do_translate({ name, _, _ }, env) do + defp do_translate({ name, _, _ }, _) do name = Utils.filter_name(name) Primitive.make_identifier(name) end diff --git a/lib/elixir_script/translator/module.ex b/lib/elixir_script/translator/module.ex index 81afa97b..f719a0df 100644 --- a/lib/elixir_script/translator/module.ex +++ b/lib/elixir_script/translator/module.ex @@ -30,8 +30,7 @@ defmodule ElixirScript.Translator.Module do def make_module(module_name_list, body, env) do body = make_inner_module_aliases(module_name_list, body) - - { body, aliases, used_stdlibs } = Aliases.process(body) + { body, aliases, used_stdlibs } = Aliases.process(body, env) { body, functions } = extract_functions_from_module(body) { exported_functions, private_functions } = process_functions(functions, env) diff --git a/test/elixir_script_test.exs b/test/elixir_script_test.exs index 1b8e01c0..d734055d 100644 --- a/test/elixir_script_test.exs +++ b/test/elixir_script_test.exs @@ -48,6 +48,7 @@ defmodule ElixirScript.Test do end should "parse multiple modules correctly" do + js_code = ElixirScript.transpile(""" defmodule Animals do @@ -61,10 +62,11 @@ defmodule ElixirScript.Test do end defp something_else() do + ElixirScript.Math.squared(1) end end - """) + """, env: make_custom_env) assert_js_matches """ import Erlang from '__lib/erlang'; @@ -76,7 +78,7 @@ defmodule ElixirScript.Test do const __MODULE__ = Erlang.atom('Animals'); let something_else = fun([[], function() { - return null; + return 1 * 1; }]); let something = fun([[], function() { From 10c6bccddc3c9cdcfa55364bbb39d0447f7c06b6 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Mon, 31 Aug 2015 18:59:35 -0500 Subject: [PATCH 06/15] Refactored the location of logger and kernel into a lib folder. Added more implementations for Kernel functions --- lib/elixir_script/lib/kernel.ex | 220 ++++++++++++++++++ .../{translator => lib}/logger.ex | 2 +- .../pattern_matching/match.ex | 2 +- lib/elixir_script/translator.ex | 14 +- lib/elixir_script/translator/assignment.ex | 2 +- lib/elixir_script/translator/capture.ex | 2 +- lib/elixir_script/translator/expression.ex | 1 - lib/elixir_script/translator/function.ex | 2 +- lib/elixir_script/translator/import.ex | 1 - lib/elixir_script/translator/kernel.ex | 99 -------- lib/elixir_script/translator/map.ex | 1 - lib/elixir_script/translator/module.ex | 1 - lib/elixir_script/translator/primitive.ex | 1 - lib/elixir_script/translator/quote.ex | 4 + lib/elixir_script/translator/struct.ex | 1 - mix.exs | 2 +- test/{translator => lib}/kernel_test.exs | 2 +- .../match_test.exs} | 4 +- test/translator/for_test.exs | 2 +- test/translator/string_test.exs | 7 +- 20 files changed, 242 insertions(+), 128 deletions(-) create mode 100644 lib/elixir_script/lib/kernel.ex rename lib/elixir_script/{translator => lib}/logger.ex (92%) rename lib/elixir_script/{translator => }/pattern_matching/match.ex (98%) delete mode 100644 lib/elixir_script/translator/kernel.ex rename test/{translator => lib}/kernel_test.exs (81%) rename test/{translator/pattern_matching_test.exs => pattern_matching/match_test.exs} (98%) diff --git a/lib/elixir_script/lib/kernel.ex b/lib/elixir_script/lib/kernel.ex new file mode 100644 index 00000000..fae869b5 --- /dev/null +++ b/lib/elixir_script/lib/kernel.ex @@ -0,0 +1,220 @@ +defmodule ElixirScript.Lib.Kernel do + @moduledoc false + alias ESTree.Tools.Builder, as: JS + alias ElixirScript.Translator + alias ElixirScript.Utils + alias ElixirScript.Translator.Function + alias ElixirScript.Translator.Expression + alias ElixirScript.Translator.If + alias ElixirScript.Translator.Raise + alias ElixirScript.Translator.Module + alias ElixirScript.Translator.Struct + alias ElixirScript.Translator.Utils + + def translate_kernel_function(name, params, env) do + do_translate({name, [], params}, env) + end + + defp do_translate({operator, _, [value]}, env) when operator in [:-, :!, :+] do + Expression.make_unary_expression(operator, value, env) + end + + defp do_translate({operator, _, [left, right]}, env) when operator in [:+, :-, :/, :*, :==, :!=, :&&, :||, :>, :<, :>=, :<=, :===, :!==] do + Expression.make_binary_expression(operator, left, right, env) + end + + defp do_translate({:<>, _, [left, right]}, env) do + Expression.make_binary_expression(:+, left, right, env) + end + + defp do_translate({:--, _, [left, right]}, env) do + + end + + defp do_translate({:++, _, [left, right]}, env) do + JS.call_expression( + JS.member_expression( + Translator.translate(left, env), + JS.identifier(:concat) + ), + [ + Translator.translate(right, env), + ] + ) + end + + defp do_translate({:.., _, [first, last]}, _) do + Translator.translate(quote do: Range.(unquote(first), unquote(last))) + end + + defp do_translate({:=~, _, [left, right]}, _) do + + end + + defp do_translate({:abs, _, [number]}, env) do + JS.call_expression( + JS.member_expression( + JS.identifier(:Map), + JS.identifier(:abs) + ), + [Translator.translate(number, env)] + ) + end + + defp do_translate({:apply, _, [fun, args]}, env) do + JS.call_expression( + JS.member_expression( + Translator.translate(fun, env), + JS.identifier(:apply) + ), + [JS.identifier(:this)] ++ Enum.map(args, &Translator.translate(&1, env)) + ) + end + + defp do_translate({:apply, _, [module, fun, args]}, env) do + JS.call_expression( + JS.member_expression( + JS.member_expression( + Translator.translate(module, env), + Translator.translate(fun, env) + ), + JS.identifier(:apply) + ), + [JS.identifier(:this)] ++ Enum.map(args, &Translator.translate(&1, env)) + ) + end + + defp do_translate({:and, _, [left, right]}, env) do + Expression.make_binary_expression(:&&, left, right, env) + end + + defp do_translate({:div, _, [left, right]}, env) do + Expression.make_binary_expression(:/, left, right, env) + end + + defp do_translate({:or, _, [left, right]}, env) do + Expression.make_binary_expression(:||, left, right, env) + end + + defp do_translate({:not, _, [value]}, env) do + Expression.make_unary_expression(:!, value, env) + end + + defp do_translate({:rem, _, [left, right]}, env) do + Expression.make_binary_expression(:%, left, right, env) + end + + defp do_translate({:round, _, [value]}, env) do + JS.call_expression( + JS.member_expression( + JS.identifier(:Math), + JS.identifier(:round) + ), + [Translator.translate(value, env)] + ) + end + + defp do_translate({:self, _, []}, _) do + JS.identifier(:self) + end + + defp do_translate({:tuple_size, _, [tuple]}, env) do + JS.member_expression( + JS.member_expression( + Translator.translate(tuple, env), + JS.identifier(:__tuple__) + ), + JS.identifier(:length) + ) + end + + defp do_translate({:map_size, _, [map]}, env) do + JS.member_expression( + JS.call_expression( + JS.member_expression( + JS.identifier(:Object), + JS.identifier(:keys) + ), + [Translator.translate(map, env)] + ), + JS.identifier(:length) + ) + end + + defp do_translate({:max, _, params}, env) do + JS.call_expression( + JS.member_expression( + JS.identifier(:Math), + JS.identifier(:max) + ), + Enum.map(params, &Translator.translate(&1, env)) + ) + end + + defp do_translate({:min, _, params}, env) do + JS.call_expression( + JS.member_expression( + JS.identifier(:Math), + JS.identifier(:min) + ), + Enum.map(params, &Translator.translate(&1, env)) + ) + end + + defp do_translate({:if, _, [test, blocks]}, _) do + If.make_if(test, blocks) + end + + defp do_translate({:|>, _, [left, right]}, _) do + case right do + {{:., meta, [module, fun]}, meta2, params} -> + Translator.translate({{:., meta, [module, fun]}, meta2, [left] ++ params}) + {fun, meta, params} -> + Translator.translate({fun, meta, [left] ++ params}) + end + end + + defp do_translate({:hd, _, [list]}, env) do + JS.member_expression( + Translator.translate(list, env), + JS.identifier(0), + true + ) + end + + defp do_translate({:tl, _, [list]}, env) do + JS.call_expression( + JS.member_expression( + Translator.translate(list, env), + JS.identifier(:splice) + ), + [1] + ) + end + + defp do_translate({:length, _, [list]}, env) when is_list(list) do + JS.member_expression( + Translator.translate(list, env), + JS.identifier(:length) + ) + end + + defp do_translate({:raise, _, [alias_info, attributes]}, _) do + {_, _, name} = alias_info + + Raise.throw_error(name, attributes) + end + + defp do_translate({:raise, _, [message]}, _) do + Raise.throw_error(message) + end + + defp do_translate({:to_string, _, [param]}, _) when is_binary(param) do + Translator.translate(param) + end + + defp do_translate({name, _, params}, env) do + Function.make_function_call(:Kernel, name, params, env) + end + +end \ No newline at end of file diff --git a/lib/elixir_script/translator/logger.ex b/lib/elixir_script/lib/logger.ex similarity index 92% rename from lib/elixir_script/translator/logger.ex rename to lib/elixir_script/lib/logger.ex index 17ca5e75..8dd4a29e 100644 --- a/lib/elixir_script/translator/logger.ex +++ b/lib/elixir_script/lib/logger.ex @@ -1,4 +1,4 @@ -defmodule ElixirScript.Translator.Logger do +defmodule ElixirScript.Lib.Logger do @moduledoc false alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator diff --git a/lib/elixir_script/translator/pattern_matching/match.ex b/lib/elixir_script/pattern_matching/match.ex similarity index 98% rename from lib/elixir_script/translator/pattern_matching/match.ex rename to lib/elixir_script/pattern_matching/match.ex index 3835bdcb..b787dc5b 100644 --- a/lib/elixir_script/translator/pattern_matching/match.ex +++ b/lib/elixir_script/pattern_matching/match.ex @@ -1,4 +1,4 @@ -defmodule ElixirScript.Translator.PatternMatching.Match do +defmodule ElixirScript.PatternMatching.Match do alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator alias ElixirScript.Translator.Utils diff --git a/lib/elixir_script/translator.ex b/lib/elixir_script/translator.ex index c3231e22..06a7a794 100644 --- a/lib/elixir_script/translator.ex +++ b/lib/elixir_script/translator.ex @@ -21,8 +21,8 @@ defmodule ElixirScript.Translator do alias ElixirScript.Translator.Receive alias ElixirScript.Translator.Quote alias ElixirScript.Translator.Utils - alias ElixirScript.Translator.Logger - alias ElixirScript.Translator.Kernel, as: ExKernel + alias ElixirScript.Lib.Logger + alias ElixirScript.Lib.Kernel, as: KernelLib alias ESTree.Tools.Builder, as: JS @doc """ @@ -67,7 +67,7 @@ defmodule ElixirScript.Translator do Function.make_anonymous_function([{:->, [], [params, body]}], env) end - defp do_translate({:@, _, [{name, _, [value]}]}, env) do + defp do_translate({:@, context, [{name, _, [value]}]}, env) do name = Utils.filter_name(name) Module.make_attribute(name, value, env) end @@ -150,7 +150,7 @@ defmodule ElixirScript.Translator do defp do_translate({{:., _, [module_name, function_name]}, _, params } = ast, env) do if module_name == Kernel do - ExKernel.translate_kernel_function(function_name, params, env) + KernelLib.translate_kernel_function(function_name, params, env) else expanded_ast = Macro.expand(ast, env) if expanded_ast == ast do @@ -255,10 +255,6 @@ defmodule ElixirScript.Translator do Function.make_anonymous_function(clauses, env) end - defp do_translate({:.., _, [first, last]}, _) do - ExKernel.make_range(first, last) - end - defp do_translate({:{}, _, elements}, env) do Primitive.make_tuple(elements, env) end @@ -289,7 +285,7 @@ defmodule ElixirScript.Translator do defp do_translate({name, metadata, params} = ast, env) when is_list(params) do if metadata[:import] == Kernel do - ExKernel.translate_kernel_function(name, params, env) + KernelLib.translate_kernel_function(name, params, env) else expanded_ast = Macro.expand(ast, env) if expanded_ast == ast do diff --git a/lib/elixir_script/translator/assignment.ex b/lib/elixir_script/translator/assignment.ex index 09b0f8d8..cdb6845b 100644 --- a/lib/elixir_script/translator/assignment.ex +++ b/lib/elixir_script/translator/assignment.ex @@ -2,7 +2,7 @@ defmodule ElixirScript.Translator.Assignment do @moduledoc false alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator - alias ElixirScript.Translator.PatternMatching.Match + alias ElixirScript.PatternMatching.Match def make_assignment(left, right, env) do { patterns, params } = Match.build_match([left]) diff --git a/lib/elixir_script/translator/capture.ex b/lib/elixir_script/translator/capture.ex index 8e7a3f2b..00a35983 100644 --- a/lib/elixir_script/translator/capture.ex +++ b/lib/elixir_script/translator/capture.ex @@ -1,6 +1,6 @@ defmodule ElixirScript.Translator.Capture do alias ESTree.Tools.Builder, as: JS - alias ElixirScript.Translator.PatternMatching.Match + alias ElixirScript.PatternMatching.Match alias ElixirScript.Translator.Utils def make_capture(function_name, arity, env) do diff --git a/lib/elixir_script/translator/expression.ex b/lib/elixir_script/translator/expression.ex index 062d45a7..e3341266 100644 --- a/lib/elixir_script/translator/expression.ex +++ b/lib/elixir_script/translator/expression.ex @@ -1,6 +1,5 @@ defmodule ElixirScript.Translator.Expression do @moduledoc false - require Logger alias ESTree.Tools.Builder alias ElixirScript.Translator diff --git a/lib/elixir_script/translator/function.ex b/lib/elixir_script/translator/function.ex index 56f9c202..0d91e265 100644 --- a/lib/elixir_script/translator/function.ex +++ b/lib/elixir_script/translator/function.ex @@ -3,7 +3,7 @@ defmodule ElixirScript.Translator.Function do alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator alias ElixirScript.Translator.Utils - alias ElixirScript.Translator.PatternMatching.Match + alias ElixirScript.PatternMatching.Match alias ElixirScript.Preprocess.Variables diff --git a/lib/elixir_script/translator/import.ex b/lib/elixir_script/translator/import.ex index c3d41d2b..abaec2c5 100644 --- a/lib/elixir_script/translator/import.ex +++ b/lib/elixir_script/translator/import.ex @@ -1,6 +1,5 @@ defmodule ElixirScript.Translator.Import do @moduledoc false - require Logger alias ESTree.Tools.Builder, as: JS def make_alias_import(alias_info, options) do diff --git a/lib/elixir_script/translator/kernel.ex b/lib/elixir_script/translator/kernel.ex deleted file mode 100644 index e573ecc7..00000000 --- a/lib/elixir_script/translator/kernel.ex +++ /dev/null @@ -1,99 +0,0 @@ -defmodule ElixirScript.Translator.Kernel do - @moduledoc false - require Logger - alias ESTree.Tools.Builder, as: JS - alias ElixirScript.Translator - alias ElixirScript.Utils - alias ElixirScript.Translator.Function - alias ElixirScript.Translator.Expression - alias ElixirScript.Translator.If - alias ElixirScript.Translator.Raise - alias ElixirScript.Translator.Module - alias ElixirScript.Translator.Struct - alias ElixirScript.Translator.Utils - - def translate_kernel_function(name, params, env) do - do_translate({name, [], params}, env) - end - - defp do_translate({operator, _, [value]}, env) when operator in [:-, :!] do - Expression.make_unary_expression(operator, value, env) - end - - defp do_translate({:<>, _, [left, right]}, env) do - Expression.make_binary_expression(:+, left, right, env) - end - - defp do_translate({:++, _, [left, right]}, env) do - JS.call_expression( - JS.member_expression( - Translator.translate(left, env), - JS.identifier(:concat) - ), - [ - Translator.translate(right, env), - ] - ) - end - - defp do_translate({operator, _, [left, right]}, env) when operator in [:+, :-, :/, :*, :==, :!=, :&&, :||, :>, :<, :>=, :<=, :===] do - Expression.make_binary_expression(operator, left, right, env) - end - - defp do_translate({:and, _, [left, right]}, env) do - Expression.make_binary_expression(:&&, left, right, env) - end - - defp do_translate({:or, _, [left, right]}, env) do - Expression.make_binary_expression(:||, left, right, env) - end - - defp do_translate({:if, _, [test, blocks]}, env) do - If.make_if(test, blocks) - end - - defp do_translate({:|>, _, [left, right]}, env) do - case right do - {{:., meta, [module, fun]}, meta2, params} -> - Translator.translate({{:., meta, [module, fun]}, meta2, [left] ++ params}) - {fun, meta, params} -> - Translator.translate({fun, meta, [left] ++ params}) - end - end - - defp do_translate({:raise, _, [alias_info, attributes]}, env) do - {_, _, name} = alias_info - - Raise.throw_error(name, attributes) - end - - defp do_translate({:raise, _, [message]}, env) do - Raise.throw_error(message) - end - - defp do_translate({:to_string, _, [param]}, env) when is_binary(param) do - Translator.translate(param) - end - - defp do_translate({name, _, params}, env) do - Function.make_function_call(:Kernel, name, params, env) - end - - def make_range(first, last) do - Translator.translate(quote do: Range.(unquote(first), unquote(last))) - end - - def concat_lists(list1, list2) do - Builder.call_expression( - Builder.member_expression( - Builder.identifier(:List), - Builder.identifier(:concat) - ), - [ - Translator.translate(list1), - Translator.translate(list2) - ] - ) - end - -end \ No newline at end of file diff --git a/lib/elixir_script/translator/map.ex b/lib/elixir_script/translator/map.ex index dd40796b..c7df6864 100644 --- a/lib/elixir_script/translator/map.ex +++ b/lib/elixir_script/translator/map.ex @@ -1,6 +1,5 @@ defmodule ElixirScript.Translator.Map do @moduledoc false - require Logger alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator alias ElixirScript.Translator.Utils diff --git a/lib/elixir_script/translator/module.ex b/lib/elixir_script/translator/module.ex index f719a0df..838b5bd7 100644 --- a/lib/elixir_script/translator/module.ex +++ b/lib/elixir_script/translator/module.ex @@ -1,6 +1,5 @@ defmodule ElixirScript.Translator.Module do @moduledoc false - require Logger alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator alias ElixirScript.Translator.Utils diff --git a/lib/elixir_script/translator/primitive.ex b/lib/elixir_script/translator/primitive.ex index 212d3af3..d4e409da 100644 --- a/lib/elixir_script/translator/primitive.ex +++ b/lib/elixir_script/translator/primitive.ex @@ -1,6 +1,5 @@ defmodule ElixirScript.Translator.Primitive do @moduledoc false - require Logger alias ESTree.Tools.Builder alias ElixirScript.Translator alias ElixirScript.Translator.Quote diff --git a/lib/elixir_script/translator/quote.ex b/lib/elixir_script/translator/quote.ex index ae1e5187..b20cbab2 100644 --- a/lib/elixir_script/translator/quote.ex +++ b/lib/elixir_script/translator/quote.ex @@ -42,6 +42,10 @@ defmodule ElixirScript.Translator.Quote do Primitive.make_tuple_quoted(opts, [:unquote, context, params]) end + def make_quote(_, {:alias!, _, [_alias]}) do + _alias + end + def make_quote(_, {:unquote, _, [param]}) do make_unquote(param) end diff --git a/lib/elixir_script/translator/struct.ex b/lib/elixir_script/translator/struct.ex index 4cb7f0c5..f0955735 100644 --- a/lib/elixir_script/translator/struct.ex +++ b/lib/elixir_script/translator/struct.ex @@ -1,6 +1,5 @@ defmodule ElixirScript.Translator.Struct do @moduledoc false - require Logger alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator diff --git a/mix.exs b/mix.exs index ac421bf6..ab673616 100644 --- a/mix.exs +++ b/mix.exs @@ -17,7 +17,7 @@ defmodule ElixirScript.Mixfile do def application do [ - applications: [:logger] + applications: [:logger, :inflex, :estree] ] end diff --git a/test/translator/kernel_test.exs b/test/lib/kernel_test.exs similarity index 81% rename from test/translator/kernel_test.exs rename to test/lib/kernel_test.exs index 5358782d..7cdaccb2 100644 --- a/test/translator/kernel_test.exs +++ b/test/lib/kernel_test.exs @@ -1,4 +1,4 @@ -defmodule ElixirScript.Translator.Kernel.Test do +defmodule ElixirScript.Lib.Kernel.Test do use ShouldI import ElixirScript.TestHelper diff --git a/test/translator/pattern_matching_test.exs b/test/pattern_matching/match_test.exs similarity index 98% rename from test/translator/pattern_matching_test.exs rename to test/pattern_matching/match_test.exs index 8b38553b..a36d7d09 100644 --- a/test/translator/pattern_matching_test.exs +++ b/test/pattern_matching/match_test.exs @@ -1,7 +1,7 @@ -defmodule ElixirScript.Translator.PatternMatching.Test do +defmodule ElixirScript.PatternMatching.Match.Test do use ShouldI alias ElixirScript.Translator - alias ElixirScript.Translator.PatternMatching.Match + alias ElixirScript.PatternMatching.Match alias ESTree.Tools.Builder, as: JS def make_list(values) when is_list(values) do diff --git a/test/translator/for_test.exs b/test/translator/for_test.exs index bed63bc7..cfb4f308 100644 --- a/test/translator/for_test.exs +++ b/test/translator/for_test.exs @@ -89,7 +89,7 @@ defmodule ElixirScript.Translator.For.Test do (function () { let [_results] = fun.bind(fun.parameter,Erlang.list()); for (let n of Erlang.list(1, 2, 3, 4, 5, 6)) { - if (Kernel.rem(n, 2) == 0) + if (n % 2 == 0) _results = List.append(_results, n); } return _results; diff --git a/test/translator/string_test.exs b/test/translator/string_test.exs index 4f17b92c..9e0bdd7f 100644 --- a/test/translator/string_test.exs +++ b/test/translator/string_test.exs @@ -24,21 +24,20 @@ defmodule ElixirScript.Translator.String.Test do ex_ast = quote do: "Hello #{"world"}" assert_translation(ex_ast, "'Hello ' + 'world'") - ex_ast = quote do: "Hello #{length([])}" - assert_translation(ex_ast, "'Hello ' + Kernel.to_string(Kernel.length(Erlang.list()))") + assert_translation(ex_ast, "'Hello ' + Kernel.to_string(Erlang.list().length)") end should "translate multiline string interpolation" do ex_ast = quote do: """ Hello #{length([])} """ - assert_translation(ex_ast, "'Hello ' + (Kernel.to_string(Kernel.length(Erlang.list())) + '\\n')") + assert_translation(ex_ast, "'Hello ' + (Kernel.to_string(Erlang.list().length) + '\\n')") ex_ast = quote do: """ Hello #{length([])} How are you, #{length([])}? """ - assert_translation(ex_ast, "'Hello ' + (Kernel.to_string(Kernel.length(Erlang.list())) + ('\\nHow are you, ' + (Kernel.to_string(Kernel.length(Erlang.list())) + '?\\n')))") + assert_translation(ex_ast, "'Hello ' + (Kernel.to_string(Erlang.list().length) + ('\\nHow are you, ' + (Kernel.to_string(Erlang.list().length) + '?\\n')))") end end \ No newline at end of file From 72ba5ad252490a9a84bc69fec28b144172002b7a Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Mon, 31 Aug 2015 19:48:28 -0500 Subject: [PATCH 07/15] Added default environment with Logger for ElixirScript.transpile. Fixed case implementation --- lib/elixir_script.ex | 9 +++++++-- lib/elixir_script/translator/case.ex | 10 +++++----- test/translator/case_test.exs | 20 ++++++++++---------- test/translator/receive_test.exs | 4 ++-- test/translator/try_test.exs | 14 +++++++------- 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/lib/elixir_script.ex b/lib/elixir_script.ex index d2f816f8..f49d5520 100644 --- a/lib/elixir_script.ex +++ b/lib/elixir_script.ex @@ -40,7 +40,7 @@ defmodule ElixirScript do def transpile_quoted(quoted, opts \\ []) do include_path = Dict.get(opts, :include_path, false) root = Dict.get(opts, :root) - env = Dict.get(opts, :env, __ENV__) + env = Dict.get(opts, :env, create_default_env) case Translator.translate(quoted, env) do modules when is_list(modules) -> @@ -55,6 +55,11 @@ defmodule ElixirScript do end end + defp create_default_env do + require Logger + __ENV__ + end + @doc """ Transpiles the elixir files found at the given path """ @@ -62,7 +67,7 @@ defmodule ElixirScript do def transpile_path(path, opts \\ []) do include_path = Dict.get(opts, :include_path, false) root = Dict.get(opts, :root) - env = Dict.get(opts, :env, __ENV__) + env = Dict.get(opts, :env, create_default_env) path |> Path.wildcard diff --git a/lib/elixir_script/translator/case.ex b/lib/elixir_script/translator/case.ex index 3a028bc7..ab6085cd 100644 --- a/lib/elixir_script/translator/case.ex +++ b/lib/elixir_script/translator/case.ex @@ -1,16 +1,16 @@ defmodule ElixirScript.Translator.Case do @moduledoc false - alias ESTree.Tools.Builder + alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator alias ElixirScript.Translator.Function def make_case(condition, clauses, env) do - Builder.call_expression( - Builder.member_expression( + JS.call_expression( + JS.member_expression( Function.make_anonymous_function(clauses, env), - Builder.identifier("call") + JS.identifier("call") ), - [Translator.translate(condition)] + [JS.identifier(:this), Translator.translate(condition)] ) end end \ No newline at end of file diff --git a/test/translator/case_test.exs b/test/translator/case_test.exs index e1fb1278..08835543 100644 --- a/test/translator/case_test.exs +++ b/test/translator/case_test.exs @@ -16,7 +16,7 @@ defmodule ElixirScript.Translator.Case.Test do return value; }], [[Erlang.atom('error')], function() { return null; - }]).call(data) + }]).call(this, data) """ assert_translation(ex_ast, js_code) @@ -34,7 +34,7 @@ defmodule ElixirScript.Translator.Case.Test do return value0; }], [[true], function() { return true; - }]).call(data) + }]).call(this, data) """ assert_translation(ex_ast, js_code) @@ -54,7 +54,7 @@ defmodule ElixirScript.Translator.Case.Test do return value0; }], [[fun.wildcard], function() { return true; - }]).call(data) + }]).call(this, data) """ assert_translation(ex_ast, js_code) @@ -88,7 +88,7 @@ defmodule ElixirScript.Translator.Case.Test do return true; } ] - ).call(data) + ).call(this, data) """ assert_translation(ex_ast, js_code) @@ -111,7 +111,7 @@ defmodule ElixirScript.Translator.Case.Test do return Todo.add(data); }], [[Erlang.atom('error')], function() { return null; - }]).call(data) + }]).call(this, data) """ assert_translation(ex_ast, js_code) @@ -132,7 +132,7 @@ defmodule ElixirScript.Translator.Case.Test do return console.info(one); }], [[Erlang.atom('error')], function() { return null; - }]).call(data) + }]).call(this, data) """ assert_translation(ex_ast, js_code) @@ -162,7 +162,7 @@ defmodule ElixirScript.Translator.Case.Test do return null; } ] - ).call(data) + ).call(this, data) """ assert_translation(ex_ast, js_code) @@ -181,7 +181,7 @@ defmodule ElixirScript.Translator.Case.Test do return console.info(one); }], [[Erlang.atom('error')], function() { return null; - }]).call(data) + }]).call(this, data) """ assert_translation(ex_ast, js_code) @@ -210,7 +210,7 @@ defmodule ElixirScript.Translator.Case.Test do return null; } ] - ).call(data) + ).call(this, data) """ assert_translation(ex_ast, js_code) @@ -239,7 +239,7 @@ defmodule ElixirScript.Translator.Case.Test do return null; } ] - ).call(data) + ).call(this, data) """ assert_translation(ex_ast, js_code) diff --git a/test/translator/receive_test.exs b/test/translator/receive_test.exs index ae49afff..93705868 100644 --- a/test/translator/receive_test.exs +++ b/test/translator/receive_test.exs @@ -22,7 +22,7 @@ defmodule ElixirScript.Translator.Receive.Test do return value; }], [[fun.wildcard], function() { return IO.puts('Unexpected message received'); - }]).call(message); + }]).call(this, message); }) """ @@ -52,7 +52,7 @@ defmodule ElixirScript.Translator.Receive.Test do return value; }], [[fun.wildcard], function() { return IO.puts('Unexpected message received'); - }]).call(message); + }]).call(this, message); }, 5000, fun([[5000], function() { diff --git a/test/translator/try_test.exs b/test/translator/try_test.exs index 4b2cc4cf..49643edd 100644 --- a/test/translator/try_test.exs +++ b/test/translator/try_test.exs @@ -30,7 +30,7 @@ defmodule ElixirScript.Translator.Try.Test do function() { return Kernel.throw(e); } - ]).call(e) + ]).call(this, e) } }.call(this)) """ @@ -68,7 +68,7 @@ defmodule ElixirScript.Translator.Try.Test do return Kernel.throw(e); } ] - ).call(e) + ).call(this, e) } }.call(this)) """ @@ -97,7 +97,7 @@ defmodule ElixirScript.Translator.Try.Test do return Kernel.__in__(x, Erlang.list(ArgumentError.defstruct())); }], [[], function() { return Kernel.throw(e); - }]).call(e) + }]).call(this, e) } }.call(this)) """ @@ -133,7 +133,7 @@ defmodule ElixirScript.Translator.Try.Test do return Kernel.throw(e); } ] - ).call(e) + ).call(this, e) } }.call(this)) """ @@ -178,7 +178,7 @@ defmodule ElixirScript.Translator.Try.Test do return Kernel.throw(e); } ] - ).call(e) + ).call(this, e) } }.call(this)) """ @@ -216,7 +216,7 @@ defmodule ElixirScript.Translator.Try.Test do return Kernel.throw(e); } ] - ).call(e) + ).call(this, e) } finally { return IO.puts('This is printed regardless if it failed or succeed'); } @@ -302,7 +302,7 @@ defmodule ElixirScript.Translator.Try.Test do return Kernel.throw(e); } ] - ).call(e) + ).call(this, e) } }.call(this)) """ From 0440a88eea92e620e24d6806a42e5d9cb5ceb8ef Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Mon, 31 Aug 2015 20:22:02 -0500 Subject: [PATCH 08/15] Added macros option to transpile functions --- CHANGELOG.md | 5 +++++ README.md | 35 +++++++++++++++++++++++++++++++---- lib/elixir_script.ex | 12 ++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f12e4084..70598443 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # v0.10.0-dev +* Enhancements + * Added `env` option for `ElixirScript.transpile` adding macros for compilation + * Fixed `case` implementation to add `this` to call + * Updated `Kernel` module to translate some functions to it's JavaScript equivalent + * Added `Logger` that translates Logger functions to console # v0.9.0 * Enhancements diff --git a/README.md b/README.md index db7280f3..490ecb69 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ ElixirScript can be used in the following ways: * If using as part of a project, you can add the following to your deps ```elixir - {:elixir_script, "~> 0.8"} + {:elixir_script, "~> 0.10"} ``` From there you can either use the ElixirScript module directly or the mix command, `mix ex2js` @@ -101,6 +101,36 @@ import Kernel from 'js/__lib/kernel' ["Erlang.list(1,2,3,4)"] ``` +# Macros + +Macros can be used by adding the environment containing the Macro module required. + +```elixir +#module with macro defined +defmodule Math do + defmacro squared(x) do + quote do + unquote(x) * unquote(x) + end + end +end + +#create an env with the module required if not already in the current enviroment +def make_custom_env do + require Logger + require Math + __ENV__ +end + + +#Now pass it to `ElixirScript.tranpile` +ElixirScript.transpile(""" + Math.squared(1) +""", env: make_custom_env) + +# returns ["1 * 1"] +``` + # Limitations @@ -122,11 +152,8 @@ The following are defined but incomplete: #### Most of the Standard Library isn't defined yet A lot of functions in the Kernel module are implemented. The Enum, Atom, List, Tuple, Logger, and Range modules are either fully defined are not complete. The rest still need to be implemented. Some modules like System or File may not be useful or function in the browser and may end up being only useful when using ElixirScript outside of the browser. -#### No Macros -Not sure how this would be implemented right now, but looking for ideas. ### Example projects - * [todo-elixirscript](https://github.com/bryanjos/example) The TodoMVC app using ElixirScript and Phoenix. * [color_bar_spike](https://github.com/bryanjos/color_bar_spike) A canvas drawing example using ElixirScript, React and Delorean diff --git a/lib/elixir_script.ex b/lib/elixir_script.ex index f49d5520..c90221d8 100644 --- a/lib/elixir_script.ex +++ b/lib/elixir_script.ex @@ -41,6 +41,12 @@ defmodule ElixirScript do include_path = Dict.get(opts, :include_path, false) root = Dict.get(opts, :root) env = Dict.get(opts, :env, create_default_env) + macros = Dict.get(opts, :macros) + + if macros do + Enum.each(macros, &Code.require_file(&1)) + env = __ENV__ + end case Translator.translate(quoted, env) do modules when is_list(modules) -> @@ -68,6 +74,12 @@ defmodule ElixirScript do include_path = Dict.get(opts, :include_path, false) root = Dict.get(opts, :root) env = Dict.get(opts, :env, create_default_env) + macros = Dict.get(opts, :macros) + + if macros do + Enum.each(macros, &Code.require_file(&1)) + env = __ENV__ + end path |> Path.wildcard From 20a1a3456e0fbcef1c0931ecd8e8b710e3793cdb Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Mon, 31 Aug 2015 21:55:49 -0500 Subject: [PATCH 09/15] Updated __DIR__ implementation. Removed unused functions in JS version of Kernel.SpecialForms --- lib/elixir_script/translator.ex | 21 ++++---- priv/javascript/lib/kernel/special_forms.js | 58 +++------------------ 2 files changed, 16 insertions(+), 63 deletions(-) diff --git a/lib/elixir_script/translator.ex b/lib/elixir_script/translator.ex index 06a7a794..8bc331ac 100644 --- a/lib/elixir_script/translator.ex +++ b/lib/elixir_script/translator.ex @@ -173,17 +173,16 @@ defmodule ElixirScript.Translator do Block.make_block(expressions) end - defp do_translate({:__DIR__, _, _expressions }, _) do - Utils.wrap_in_function_closure( - JS.if_statement( - JS.identifier(:__dirname), - JS.block_statement([ - JS.return_statement(JS.identifier(:__dirname)) - ]), - JS.block_statement([ - JS.return_statement(JS.literal(nil)) - ]) - ) + defp do_translate({:__DIR__, _, _}, _) do + JS.call_expression( + JS.member_expression( + JS.member_expression( + JS.identifier(:Kernel), + JS.identifier(:SpecialForms) + ), + JS.identifier(:__DIR__) + ), + [] ) end diff --git a/priv/javascript/lib/kernel/special_forms.js b/priv/javascript/lib/kernel/special_forms.js index 55e61023..baca8606 100644 --- a/priv/javascript/lib/kernel/special_forms.js +++ b/priv/javascript/lib/kernel/special_forms.js @@ -1,64 +1,18 @@ import Erlang from '../erlang'; -import fun from '../funcy/fun'; let SpecialForms = { __MODULE__: Erlang.atom('SpecialForms'), - case: function(condition, clauses){ - return fun(clauses).call(condition); - }, - - fn: function(clauses){ - return fun(clauses); - }, - - cond: function(clauses){ - for(let clause in clauses){ - if(clause[0]){ - return clause[1](); - } - } - }, - - import: function(module, opts){ - let imported_module = SpecialForms.alias(module); - - if(opts.length === 0){ - return imported_module; - }else if(opts[Erlang.atom("only")]){ - let exported = {}; - for(let item of opts[Erlang.atom("only")]){ - let key = Symbol.keyFor(item.get(0)); - exported[key] = imported_module[key]; - } - - return exported; - }else if(opts[Erlang.atom("except")]){ - let exported = {}; - let except_list = opts[Erlang.atom("except")]; - - for(let [key, value] of imported_module){ - for(let i = 0; i < except_list.length; i++){ - if(except_list[i] === Erlang.atom(key)){ - exported[key] = imported_module[key]; - } - } - } - - return exported; + __DIR__: function(){ + if(__dirname){ + return __dirname; } - }, - - alias: function(module, opts){ - return System.import(module).resolve(); - }, - require: function(module, opts){ - if(module === undefined){ - throw new Error("module is not loaded and could not be found"); + if(document.currentScript){ + return document.currentScript.src; } - SpecialForms.alias(module, opts); + return null; }, receive: function(receive_fun, timeout_in_ms = null, timeout_fn = (time) => true){ From 1c8b7d85c82d6e0c55caba6e8d537e96fab8e98a Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Tue, 1 Sep 2015 08:41:37 -0500 Subject: [PATCH 10/15] Removed requires/macros option --- README.md | 2 +- lib/elixir_script.ex | 30 +++++++----------------------- lib/elixir_script/cli.ex | 26 +++++++++++++------------- test/translator/defmodule_test.exs | 20 -------------------- 4 files changed, 21 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 490ecb69..4f912045 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ import Kernel from 'js/__lib/kernel' # Macros -Macros can be used by adding the environment containing the Macro module required. +Macros can be used when using ElixirScript as a library if the Macros are loaded into the current environment or if you give it a custom environment with the `env` option ```elixir #module with macro defined diff --git a/lib/elixir_script.ex b/lib/elixir_script.ex index c90221d8..c518312e 100644 --- a/lib/elixir_script.ex +++ b/lib/elixir_script.ex @@ -3,6 +3,7 @@ defmodule ElixirScript do alias ElixirScript.Translator.JSModule alias ESTree.Tools.Builder alias ESTree.Tools.Generator + require Logger @moduledoc """ Transpiles Elixir into JavaScript. @@ -15,12 +16,12 @@ defmodule ElixirScript do that controls transpiler output. Available options are: - * include_path: a boolean controlling whether to return just the JavaScript code + * `: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 + * `: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 """ @@ -40,13 +41,7 @@ defmodule ElixirScript do def transpile_quoted(quoted, opts \\ []) do include_path = Dict.get(opts, :include_path, false) root = Dict.get(opts, :root) - env = Dict.get(opts, :env, create_default_env) - macros = Dict.get(opts, :macros) - - if macros do - Enum.each(macros, &Code.require_file(&1)) - env = __ENV__ - end + env = Dict.get(opts, :env, __ENV__) case Translator.translate(quoted, env) do modules when is_list(modules) -> @@ -61,11 +56,6 @@ defmodule ElixirScript do end end - defp create_default_env do - require Logger - __ENV__ - end - @doc """ Transpiles the elixir files found at the given path """ @@ -73,13 +63,7 @@ defmodule ElixirScript do def transpile_path(path, opts \\ []) do include_path = Dict.get(opts, :include_path, false) root = Dict.get(opts, :root) - env = Dict.get(opts, :env, create_default_env) - macros = Dict.get(opts, :macros) - - if macros do - Enum.each(macros, &Code.require_file(&1)) - env = __ENV__ - end + env = Dict.get(opts, :env, __ENV__) path |> Path.wildcard diff --git a/lib/elixir_script/cli.ex b/lib/elixir_script/cli.ex index 454c8b9f..c090b232 100644 --- a/lib/elixir_script/cli.ex +++ b/lib/elixir_script/cli.ex @@ -1,23 +1,23 @@ defmodule ElixirScript.CLI do @moduledoc false + @switches [ + output: :binary, elixir: :boolean, root: :binary, + help: :boolean + ] + + @aliases [ + o: :output, ex: :elixir, h: :help, r: :root + ] + def main(argv) do argv |> parse_args |> process end - def parse_args(args) do - switches = [ - output: :binary, elixir: :boolean, root: :binary, - help: :boolean - ] - - aliases = [ - o: :output, ex: :elixir, h: :help, r: :root - ] - - parse = OptionParser.parse(args, switches: switches, aliases: aliases) + def parse_args(args) do + parse = OptionParser.parse(args, switches: @switches, aliases: @aliases) case parse do { [help: true] , _ , _ } -> :help @@ -50,7 +50,7 @@ defmodule ElixirScript.CLI do def do_process(input, options) do transpile_opts = [ root: options[:root], - include_path: options[:output] != nil + include_path: options[:output] != nil ] transpile_output = case options[:elixir] do @@ -78,7 +78,7 @@ defmodule ElixirScript.CLI do defp options_contains_unknown_values(options) do Enum.any?(options, fn({key, _value}) -> - if key in [:output, :elixir, :root, :help] do + if key in Keyword.keys(@switches) do false else true diff --git a/test/translator/defmodule_test.exs b/test/translator/defmodule_test.exs index bddb723a..8feba63d 100644 --- a/test/translator/defmodule_test.exs +++ b/test/translator/defmodule_test.exs @@ -350,24 +350,4 @@ defmodule ElixirScript.Translator.Defmodule.Test do assert_translation(ex_ast, js_code) end - - should "ignore standard libs" do - ex_ast = quote do - defmodule Animals do - Kernel.hd([1]) - Kernel.SpecialForms.alias(Dog, []) - end - end - - js_code = """ - const __MODULE__ = Erlang.atom('Animals'); - - Kernel.hd(Erlang.list(1)); - Kernel.SpecialForms.alias(Dog, Erlang.list()); - - export default {}; - """ - - assert_translation(ex_ast, js_code) - end end From 6ba4a66b413ffb7cdaf8ec8a9d6a86a9d5fc7282 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Tue, 1 Sep 2015 17:18:53 -0500 Subject: [PATCH 11/15] Expanding __using__ macros into the module they are defined in --- lib/elixir_script/preprocess/using.ex | 27 ++++++++++++++++ lib/elixir_script/translator.ex | 4 +-- lib/elixir_script/translator/block.ex | 4 +-- lib/elixir_script/translator/module.ex | 2 ++ test/elixir_script_test.exs | 45 +++++++++++++++----------- test/test_helper.exs | 12 +++++++ 6 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 lib/elixir_script/preprocess/using.ex diff --git a/lib/elixir_script/preprocess/using.ex b/lib/elixir_script/preprocess/using.ex new file mode 100644 index 00000000..f00febdc --- /dev/null +++ b/lib/elixir_script/preprocess/using.ex @@ -0,0 +1,27 @@ +defmodule ElixirScript.Preprocess.Using do + + + def process(ast, env) do + Macro.prewalk(ast, fn(x) -> + process_using(x, env) + end) + end + + def process_using({:use, _, [{:__aliases__, _, module_name}, params]} = ast, env) do + expanded_ast = Macro.expand(ast, env) + eval_using(expanded_ast, env) + end + + def process_using({:use, context, [{:__aliases__, context2, module_name}]}, env) do + process_using({:use, context, [{:__aliases__, context2, module_name}, []]}, env) + end + + def process_using(ast, env) do + ast + end + + defp eval_using({:__block__, _, [{:require, _, _} = require_ast , {{:., _, [module_name, :__using__]}, _, _} = using_ast]}, env) do + Macro.expand_once(using_ast, env) + end + +end \ No newline at end of file diff --git a/lib/elixir_script/translator.ex b/lib/elixir_script/translator.ex index 8bc331ac..e2cbb0cb 100644 --- a/lib/elixir_script/translator.ex +++ b/lib/elixir_script/translator.ex @@ -169,8 +169,8 @@ defmodule ElixirScript.Translator do Primitive.make_identifier(aliases) end - defp do_translate({:__block__, _, expressions }, _) do - Block.make_block(expressions) + defp do_translate({:__block__, _, expressions }, env) do + Block.make_block(expressions, env) end defp do_translate({:__DIR__, _, _}, _) do diff --git a/lib/elixir_script/translator/block.ex b/lib/elixir_script/translator/block.ex index b435e5d0..5bd958f9 100644 --- a/lib/elixir_script/translator/block.ex +++ b/lib/elixir_script/translator/block.ex @@ -3,8 +3,8 @@ defmodule ElixirScript.Translator.Block do alias ESTree.Tools.Builder alias ElixirScript.Translator - def make_block(expressions) do - Builder.block_statement(Enum.map(expressions, &Translator.translate(&1))) + def make_block(expressions, env) do + Builder.block_statement(Enum.map(expressions, &Translator.translate(&1, env))) end end \ No newline at end of file diff --git a/lib/elixir_script/translator/module.ex b/lib/elixir_script/translator/module.ex index 838b5bd7..c7919763 100644 --- a/lib/elixir_script/translator/module.ex +++ b/lib/elixir_script/translator/module.ex @@ -5,6 +5,7 @@ defmodule ElixirScript.Translator.Module do alias ElixirScript.Translator.Utils alias ElixirScript.Translator.JSModule alias ElixirScript.Preprocess.Aliases + alias ElixirScript.Preprocess.Using alias ElixirScript.Translator.Function @standard_libs [ @@ -29,6 +30,7 @@ defmodule ElixirScript.Translator.Module do def make_module(module_name_list, body, env) do body = make_inner_module_aliases(module_name_list, body) + body = Using.process(body, env) { body, aliases, used_stdlibs } = Aliases.process(body, env) { body, functions } = extract_functions_from_module(body) diff --git a/test/elixir_script_test.exs b/test/elixir_script_test.exs index d734055d..d1a7263f 100644 --- a/test/elixir_script_test.exs +++ b/test/elixir_script_test.exs @@ -9,6 +9,7 @@ defmodule ElixirScript.Test do should "parse one module correctly" do js_code = ElixirScript.transpile(""" + defmodule Elephant do @ul JQuery.("#todo-list") @@ -51,6 +52,7 @@ defmodule ElixirScript.Test do js_code = ElixirScript.transpile(""" defmodule Animals do + use ElixirScript.Using, async: true defmodule Elephant do defstruct trunk: true @@ -69,25 +71,30 @@ defmodule ElixirScript.Test do """, env: make_custom_env) assert_js_matches """ - import Erlang from '__lib/erlang'; - import Enum from '__lib/enum'; - import Kernel from '__lib/kernel'; - import Tuple from '__lib/tuple'; - import fun from '__lib/funcy/fun'; - import Elephant from 'animals/elephant'; - const __MODULE__ = Erlang.atom('Animals'); - - let something_else = fun([[], function() { - return 1 * 1; - }]); - - let something = fun([[], function() { - return Elephant.defstruct(); - }]); - - export default { - something: something - }; + import Erlang from '__lib/erlang'; + import Enum from '__lib/enum'; + import Kernel from '__lib/kernel'; + import Tuple from '__lib/tuple'; + import fun from '__lib/funcy/fun'; + import Elephant from 'animals/elephant'; + const __MODULE__ = Erlang.atom('Animals'); + + let something_else = fun([[], function() { + return 1 * 1; + }]); + + let something = fun([[], function() { + return Elephant.defstruct(); + }]); + + let sandwich = fun([[], function() { + return null; + }]); + + export default { + something: something, + sandwich: sandwich + }; """, hd(js_code) assert_js_matches """ diff --git a/test/test_helper.exs b/test/test_helper.exs index bab1254a..3c25376a 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -10,12 +10,24 @@ defmodule ElixirScript.Math do end end +defmodule ElixirScript.Using do + defmacro __using__(_) do + quote do + def sandwich() do + end + end + end +end + defmodule ElixirScript.TestHelper do use ShouldI + require Logger def make_custom_env do require Logger require ElixirScript.Math + require ElixirScript.Using + __ENV__ end From 526e6013cad84a4bb62bfe3b966f26cb67d3ca44 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Tue, 1 Sep 2015 17:25:37 -0500 Subject: [PATCH 12/15] Removed warnings --- README.md | 2 ++ lib/elixir_script/lib/kernel.ex | 12 ------------ lib/elixir_script/preprocess/using.ex | 15 ++++++--------- lib/elixir_script/translator.ex | 2 +- lib/elixir_script/translator/capture.ex | 2 +- 5 files changed, 10 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4f912045..ab205691 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,8 @@ ElixirScript.transpile(""" # returns ["1 * 1"] ``` +You should be able to use `use` in modules now as well, but modules that have `__using__` macros must also be require'd so that they can be expanded. + # Limitations diff --git a/lib/elixir_script/lib/kernel.ex b/lib/elixir_script/lib/kernel.ex index fae869b5..11044a66 100644 --- a/lib/elixir_script/lib/kernel.ex +++ b/lib/elixir_script/lib/kernel.ex @@ -2,14 +2,10 @@ defmodule ElixirScript.Lib.Kernel do @moduledoc false alias ESTree.Tools.Builder, as: JS alias ElixirScript.Translator - alias ElixirScript.Utils alias ElixirScript.Translator.Function alias ElixirScript.Translator.Expression alias ElixirScript.Translator.If alias ElixirScript.Translator.Raise - alias ElixirScript.Translator.Module - alias ElixirScript.Translator.Struct - alias ElixirScript.Translator.Utils def translate_kernel_function(name, params, env) do do_translate({name, [], params}, env) @@ -27,10 +23,6 @@ defmodule ElixirScript.Lib.Kernel do Expression.make_binary_expression(:+, left, right, env) end - defp do_translate({:--, _, [left, right]}, env) do - - end - defp do_translate({:++, _, [left, right]}, env) do JS.call_expression( JS.member_expression( @@ -47,10 +39,6 @@ defmodule ElixirScript.Lib.Kernel do Translator.translate(quote do: Range.(unquote(first), unquote(last))) end - defp do_translate({:=~, _, [left, right]}, _) do - - end - defp do_translate({:abs, _, [number]}, env) do JS.call_expression( JS.member_expression( diff --git a/lib/elixir_script/preprocess/using.ex b/lib/elixir_script/preprocess/using.ex index f00febdc..1044bc15 100644 --- a/lib/elixir_script/preprocess/using.ex +++ b/lib/elixir_script/preprocess/using.ex @@ -7,20 +7,17 @@ defmodule ElixirScript.Preprocess.Using do end) end - def process_using({:use, _, [{:__aliases__, _, module_name}, params]} = ast, env) do - expanded_ast = Macro.expand(ast, env) - eval_using(expanded_ast, env) - end - - def process_using({:use, context, [{:__aliases__, context2, module_name}]}, env) do - process_using({:use, context, [{:__aliases__, context2, module_name}, []]}, env) + def process_using({:use, _, _} = ast, env) do + ast + |> Macro.expand(env) + |> expand__using__(env) end - def process_using(ast, env) do + def process_using(ast, _) do ast end - defp eval_using({:__block__, _, [{:require, _, _} = require_ast , {{:., _, [module_name, :__using__]}, _, _} = using_ast]}, env) do + defp expand__using__({:__block__, _, [{:require, _, _}, {{:., _, [_, :__using__]}, _, _} = using_ast]}, env) do Macro.expand_once(using_ast, env) end diff --git a/lib/elixir_script/translator.ex b/lib/elixir_script/translator.ex index e2cbb0cb..05f2791d 100644 --- a/lib/elixir_script/translator.ex +++ b/lib/elixir_script/translator.ex @@ -67,7 +67,7 @@ defmodule ElixirScript.Translator do Function.make_anonymous_function([{:->, [], [params, body]}], env) end - defp do_translate({:@, context, [{name, _, [value]}]}, env) do + defp do_translate({:@, _, [{name, _, [value]}]}, env) do name = Utils.filter_name(name) Module.make_attribute(name, value, env) end diff --git a/lib/elixir_script/translator/capture.ex b/lib/elixir_script/translator/capture.ex index 00a35983..92273491 100644 --- a/lib/elixir_script/translator/capture.ex +++ b/lib/elixir_script/translator/capture.ex @@ -3,7 +3,7 @@ defmodule ElixirScript.Translator.Capture do alias ElixirScript.PatternMatching.Match alias ElixirScript.Translator.Utils - def make_capture(function_name, arity, env) do + def make_capture(function_name, arity, _) do params = Enum.map(1..arity, fn(x) -> {String.to_atom("__#{x}"), [], ElixirScript.Translator.Capture} end) { patterns, params } = Match.build_match(params) From b0fcd0107dd30355470464bf59f2e5023517e9bb Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Tue, 1 Sep 2015 17:34:21 -0500 Subject: [PATCH 13/15] Updated the logger implementation to only add the first parameter to the underlying console call --- lib/elixir_script/lib/logger.ex | 2 +- test/elixir_script_test.exs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/elixir_script/lib/logger.ex b/lib/elixir_script/lib/logger.ex index 8dd4a29e..c6aed394 100644 --- a/lib/elixir_script/lib/logger.ex +++ b/lib/elixir_script/lib/logger.ex @@ -18,7 +18,7 @@ defmodule ElixirScript.Lib.Logger do JS.identifier("console"), JS.identifier(level) ), - Enum.map(params, fn(x) -> Translator.translate(x, env) end) + [Translator.translate(hd(params), env)] ) end end \ No newline at end of file diff --git a/test/elixir_script_test.exs b/test/elixir_script_test.exs index d1a7263f..e707a8a1 100644 --- a/test/elixir_script_test.exs +++ b/test/elixir_script_test.exs @@ -12,6 +12,7 @@ defmodule ElixirScript.Test do defmodule Elephant do @ul JQuery.("#todo-list") + Logger.debug("heelo") def something() do @ul From 9d5879afbe6b3291536bd8e0ab00ca3ddd97218d Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Tue, 1 Sep 2015 17:37:09 -0500 Subject: [PATCH 14/15] Removed -dev on version for release --- CHANGELOG.md | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70598443..b61ae1ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# v0.10.0-dev +# v0.10.0 * Enhancements * Added `env` option for `ElixirScript.transpile` adding macros for compilation * Fixed `case` implementation to add `this` to call diff --git a/mix.exs b/mix.exs index ab673616..008a9d08 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule ElixirScript.Mixfile do def project do [ app: :elixir_script, - version: "0.10.0-dev", + version: "0.10.0", elixir: "~> 1.0", escript: escript_config, deps: deps, From 608feabd5a83c57a2fececde8e91b6fbc2efab66 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Tue, 1 Sep 2015 17:38:41 -0500 Subject: [PATCH 15/15] Fixed broken test --- test/elixir_script_test.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/elixir_script_test.exs b/test/elixir_script_test.exs index e707a8a1..d1a7263f 100644 --- a/test/elixir_script_test.exs +++ b/test/elixir_script_test.exs @@ -12,7 +12,6 @@ defmodule ElixirScript.Test do defmodule Elephant do @ul JQuery.("#todo-list") - Logger.debug("heelo") def something() do @ul