Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# 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
* Updated `Kernel` module to translate some functions to it's JavaScript equivalent
* Added `Logger` that translates Logger functions to console

# v0.9.0
* Enhancements
Expand Down
37 changes: 33 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -101,6 +101,38 @@ import Kernel from 'js/__lib/kernel'
["Erlang.list(1,2,3,4)"]
```

# Macros

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
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"]
```

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

Expand All @@ -122,11 +154,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
Expand Down
13 changes: 9 additions & 4 deletions lib/elixir_script.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -15,10 +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
* `: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 """
Expand All @@ -38,8 +41,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) ->
Expand All @@ -59,13 +63,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) ->
Expand Down
26 changes: 13 additions & 13 deletions lib/elixir_script/cli.ex
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
208 changes: 208 additions & 0 deletions lib/elixir_script/lib/kernel.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
defmodule ElixirScript.Lib.Kernel do
@moduledoc false
alias ESTree.Tools.Builder, as: JS
alias ElixirScript.Translator
alias ElixirScript.Translator.Function
alias ElixirScript.Translator.Expression
alias ElixirScript.Translator.If
alias ElixirScript.Translator.Raise

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
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({: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
Loading