Skip to content

Commit ab0417e

Browse files
committed
Merge pull request elixirscript#81 from bryanjos/macros
Macros
2 parents f7ba5c4 + 608feab commit ab0417e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+764
-530
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
# v0.10.0-dev
1+
# v0.10.0
2+
* Enhancements
3+
* Added `env` option for `ElixirScript.transpile` adding macros for compilation
4+
* Fixed `case` implementation to add `this` to call
5+
* Updated `Kernel` module to translate some functions to it's JavaScript equivalent
6+
* Added `Logger` that translates Logger functions to console
27

38
# v0.9.0
49
* Enhancements

README.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ ElixirScript can be used in the following ways:
1616
* If using as part of a project, you can add the following to your deps
1717

1818
```elixir
19-
{:elixir_script, "~> 0.8"}
19+
{:elixir_script, "~> 0.10"}
2020
```
2121

2222
From there you can either use the ElixirScript module directly or the mix command, `mix ex2js`
@@ -101,6 +101,38 @@ import Kernel from 'js/__lib/kernel'
101101
["Erlang.list(1,2,3,4)"]
102102
```
103103

104+
# Macros
105+
106+
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
107+
108+
```elixir
109+
#module with macro defined
110+
defmodule Math do
111+
defmacro squared(x) do
112+
quote do
113+
unquote(x) * unquote(x)
114+
end
115+
end
116+
end
117+
118+
#create an env with the module required if not already in the current enviroment
119+
def make_custom_env do
120+
require Logger
121+
require Math
122+
__ENV__
123+
end
124+
125+
126+
#Now pass it to `ElixirScript.tranpile`
127+
ElixirScript.transpile("""
128+
Math.squared(1)
129+
""", env: make_custom_env)
130+
131+
# returns ["1 * 1"]
132+
```
133+
134+
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.
135+
104136
105137
# Limitations
106138
@@ -122,11 +154,8 @@ The following are defined but incomplete:
122154
#### Most of the Standard Library isn't defined yet
123155
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.
124156

125-
#### No Macros
126-
Not sure how this would be implemented right now, but looking for ideas.
127157

128158
### Example projects
129-
130159
* [todo-elixirscript](https://github.com/bryanjos/example) The TodoMVC app using ElixirScript and Phoenix.
131160

132161
* [color_bar_spike](https://github.com/bryanjos/color_bar_spike) A canvas drawing example using ElixirScript, React and Delorean

lib/elixir_script.ex

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ defmodule ElixirScript do
33
alias ElixirScript.Translator.JSModule
44
alias ESTree.Tools.Builder
55
alias ESTree.Tools.Generator
6+
require Logger
67

78
@moduledoc """
89
Transpiles Elixir into JavaScript.
@@ -15,10 +16,12 @@ defmodule ElixirScript do
1516
that controls transpiler output.
1617
1718
Available options are:
18-
* include_path: a boolean controlling whether to return just the JavaScript code
19+
* `:include_path` - a boolean controlling whether to return just the JavaScript code
1920
or a tuple of the file name and the JavaScript code
2021
21-
* root: a binary path prepended to the path of the standard lib imports if needed
22+
* `:root` - a binary path prepended to the path of the standard lib imports if needed
23+
* `:env` - a Macro.env struct to use. This is most useful when using macros. Make sure that the
24+
given env has the macros required. Defaults to __ENV__.
2225
"""
2326

2427
@doc """
@@ -38,8 +41,9 @@ defmodule ElixirScript do
3841
def transpile_quoted(quoted, opts \\ []) do
3942
include_path = Dict.get(opts, :include_path, false)
4043
root = Dict.get(opts, :root)
44+
env = Dict.get(opts, :env, __ENV__)
4145

42-
case Translator.translate(quoted) do
46+
case Translator.translate(quoted, env) do
4347
modules when is_list(modules) ->
4448
List.flatten(modules)
4549
|> Enum.map(fn(x) ->
@@ -59,13 +63,14 @@ defmodule ElixirScript do
5963
def transpile_path(path, opts \\ []) do
6064
include_path = Dict.get(opts, :include_path, false)
6165
root = Dict.get(opts, :root)
66+
env = Dict.get(opts, :env, __ENV__)
6267

6368
path
6469
|> Path.wildcard
6570
|> Enum.map(fn(x) ->
6671
File.read!(x)
6772
|> Code.string_to_quoted!
68-
|> Translator.translate
73+
|> Translator.translate(env)
6974
end)
7075
|> List.flatten
7176
|> Enum.map(fn(x) ->

lib/elixir_script/cli.ex

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
defmodule ElixirScript.CLI do
22
@moduledoc false
33

4+
@switches [
5+
output: :binary, elixir: :boolean, root: :binary,
6+
help: :boolean
7+
]
8+
9+
@aliases [
10+
o: :output, ex: :elixir, h: :help, r: :root
11+
]
12+
413
def main(argv) do
514
argv
615
|> parse_args
716
|> process
817
end
918

10-
def parse_args(args) do
11-
switches = [
12-
output: :binary, elixir: :boolean, root: :binary,
13-
help: :boolean
14-
]
15-
16-
aliases = [
17-
o: :output, ex: :elixir, h: :help, r: :root
18-
]
19-
20-
parse = OptionParser.parse(args, switches: switches, aliases: aliases)
19+
def parse_args(args) do
20+
parse = OptionParser.parse(args, switches: @switches, aliases: @aliases)
2121

2222
case parse do
2323
{ [help: true] , _ , _ } -> :help
@@ -50,7 +50,7 @@ defmodule ElixirScript.CLI do
5050
def do_process(input, options) do
5151
transpile_opts = [
5252
root: options[:root],
53-
include_path: options[:output] != nil
53+
include_path: options[:output] != nil
5454
]
5555

5656
transpile_output = case options[:elixir] do
@@ -78,7 +78,7 @@ defmodule ElixirScript.CLI do
7878

7979
defp options_contains_unknown_values(options) do
8080
Enum.any?(options, fn({key, _value}) ->
81-
if key in [:output, :elixir, :root, :help] do
81+
if key in Keyword.keys(@switches) do
8282
false
8383
else
8484
true

lib/elixir_script/lib/kernel.ex

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
defmodule ElixirScript.Lib.Kernel do
2+
@moduledoc false
3+
alias ESTree.Tools.Builder, as: JS
4+
alias ElixirScript.Translator
5+
alias ElixirScript.Translator.Function
6+
alias ElixirScript.Translator.Expression
7+
alias ElixirScript.Translator.If
8+
alias ElixirScript.Translator.Raise
9+
10+
def translate_kernel_function(name, params, env) do
11+
do_translate({name, [], params}, env)
12+
end
13+
14+
defp do_translate({operator, _, [value]}, env) when operator in [:-, :!, :+] do
15+
Expression.make_unary_expression(operator, value, env)
16+
end
17+
18+
defp do_translate({operator, _, [left, right]}, env) when operator in [:+, :-, :/, :*, :==, :!=, :&&, :||, :>, :<, :>=, :<=, :===, :!==] do
19+
Expression.make_binary_expression(operator, left, right, env)
20+
end
21+
22+
defp do_translate({:<>, _, [left, right]}, env) do
23+
Expression.make_binary_expression(:+, left, right, env)
24+
end
25+
26+
defp do_translate({:++, _, [left, right]}, env) do
27+
JS.call_expression(
28+
JS.member_expression(
29+
Translator.translate(left, env),
30+
JS.identifier(:concat)
31+
),
32+
[
33+
Translator.translate(right, env),
34+
]
35+
)
36+
end
37+
38+
defp do_translate({:.., _, [first, last]}, _) do
39+
Translator.translate(quote do: Range.(unquote(first), unquote(last)))
40+
end
41+
42+
defp do_translate({:abs, _, [number]}, env) do
43+
JS.call_expression(
44+
JS.member_expression(
45+
JS.identifier(:Map),
46+
JS.identifier(:abs)
47+
),
48+
[Translator.translate(number, env)]
49+
)
50+
end
51+
52+
defp do_translate({:apply, _, [fun, args]}, env) do
53+
JS.call_expression(
54+
JS.member_expression(
55+
Translator.translate(fun, env),
56+
JS.identifier(:apply)
57+
),
58+
[JS.identifier(:this)] ++ Enum.map(args, &Translator.translate(&1, env))
59+
)
60+
end
61+
62+
defp do_translate({:apply, _, [module, fun, args]}, env) do
63+
JS.call_expression(
64+
JS.member_expression(
65+
JS.member_expression(
66+
Translator.translate(module, env),
67+
Translator.translate(fun, env)
68+
),
69+
JS.identifier(:apply)
70+
),
71+
[JS.identifier(:this)] ++ Enum.map(args, &Translator.translate(&1, env))
72+
)
73+
end
74+
75+
defp do_translate({:and, _, [left, right]}, env) do
76+
Expression.make_binary_expression(:&&, left, right, env)
77+
end
78+
79+
defp do_translate({:div, _, [left, right]}, env) do
80+
Expression.make_binary_expression(:/, left, right, env)
81+
end
82+
83+
defp do_translate({:or, _, [left, right]}, env) do
84+
Expression.make_binary_expression(:||, left, right, env)
85+
end
86+
87+
defp do_translate({:not, _, [value]}, env) do
88+
Expression.make_unary_expression(:!, value, env)
89+
end
90+
91+
defp do_translate({:rem, _, [left, right]}, env) do
92+
Expression.make_binary_expression(:%, left, right, env)
93+
end
94+
95+
defp do_translate({:round, _, [value]}, env) do
96+
JS.call_expression(
97+
JS.member_expression(
98+
JS.identifier(:Math),
99+
JS.identifier(:round)
100+
),
101+
[Translator.translate(value, env)]
102+
)
103+
end
104+
105+
defp do_translate({:self, _, []}, _) do
106+
JS.identifier(:self)
107+
end
108+
109+
defp do_translate({:tuple_size, _, [tuple]}, env) do
110+
JS.member_expression(
111+
JS.member_expression(
112+
Translator.translate(tuple, env),
113+
JS.identifier(:__tuple__)
114+
),
115+
JS.identifier(:length)
116+
)
117+
end
118+
119+
defp do_translate({:map_size, _, [map]}, env) do
120+
JS.member_expression(
121+
JS.call_expression(
122+
JS.member_expression(
123+
JS.identifier(:Object),
124+
JS.identifier(:keys)
125+
),
126+
[Translator.translate(map, env)]
127+
),
128+
JS.identifier(:length)
129+
)
130+
end
131+
132+
defp do_translate({:max, _, params}, env) do
133+
JS.call_expression(
134+
JS.member_expression(
135+
JS.identifier(:Math),
136+
JS.identifier(:max)
137+
),
138+
Enum.map(params, &Translator.translate(&1, env))
139+
)
140+
end
141+
142+
defp do_translate({:min, _, params}, env) do
143+
JS.call_expression(
144+
JS.member_expression(
145+
JS.identifier(:Math),
146+
JS.identifier(:min)
147+
),
148+
Enum.map(params, &Translator.translate(&1, env))
149+
)
150+
end
151+
152+
defp do_translate({:if, _, [test, blocks]}, _) do
153+
If.make_if(test, blocks)
154+
end
155+
156+
defp do_translate({:|>, _, [left, right]}, _) do
157+
case right do
158+
{{:., meta, [module, fun]}, meta2, params} ->
159+
Translator.translate({{:., meta, [module, fun]}, meta2, [left] ++ params})
160+
{fun, meta, params} ->
161+
Translator.translate({fun, meta, [left] ++ params})
162+
end
163+
end
164+
165+
defp do_translate({:hd, _, [list]}, env) do
166+
JS.member_expression(
167+
Translator.translate(list, env),
168+
JS.identifier(0),
169+
true
170+
)
171+
end
172+
173+
defp do_translate({:tl, _, [list]}, env) do
174+
JS.call_expression(
175+
JS.member_expression(
176+
Translator.translate(list, env),
177+
JS.identifier(:splice)
178+
),
179+
[1]
180+
)
181+
end
182+
183+
defp do_translate({:length, _, [list]}, env) when is_list(list) do
184+
JS.member_expression(
185+
Translator.translate(list, env),
186+
JS.identifier(:length)
187+
)
188+
end
189+
190+
defp do_translate({:raise, _, [alias_info, attributes]}, _) do
191+
{_, _, name} = alias_info
192+
193+
Raise.throw_error(name, attributes)
194+
end
195+
196+
defp do_translate({:raise, _, [message]}, _) do
197+
Raise.throw_error(message)
198+
end
199+
200+
defp do_translate({:to_string, _, [param]}, _) when is_binary(param) do
201+
Translator.translate(param)
202+
end
203+
204+
defp do_translate({name, _, params}, env) do
205+
Function.make_function_call(:Kernel, name, params, env)
206+
end
207+
208+
end

0 commit comments

Comments
 (0)