Change how -> is represented in the AST

The previous representation was very hard to compose
and very often required the AST nodes to be created
manually.

The current approach is easier to compose because
the node is represented as any other operator as long
as it is wrapped in parentheses.

For example, imagine we have pairs of patterns and
the code to be executed for that pattern, and we want
to inject them into a case. It can now be writen as:

    pairs = Enum.map pairs, fn { pattern, expr } ->
      quote do: (unquote(pattern) -> unquote(expr))
    end

    quote do
      case unquote(condition), do: unquote(pairs)
    end

Compare to the previous implementation:

    pairs = Enum.map pairs, fn { pattern, expr } ->
      { [pattern], [], expr }
    end

    pairs = { :->, [], pairs }

    quote do
      case unquote(condition), do: unquote(pairs)
    end
This commit is contained in:
José Valim
2013-12-23 16:10:33 +01:00
parent 2d36a76b26
commit 51aef55c83
9 changed files with 60 additions and 47 deletions
+7 -9
View File
@@ -2173,8 +2173,8 @@ defmodule Kernel do
"This will"
end
"""
defmacro cond([do: { :->, _, pairs }]) do
[{ [condition], meta, clause }|t] = :lists.reverse pairs
defmacro cond([do: pairs]) do
[{ :->, meta, [[condition], clause] }|t] = :lists.reverse pairs
new_acc =
case condition do
@@ -2205,22 +2205,20 @@ defmodule Kernel do
# end
# end
#
defp build_cond_clauses([{ [condition], new, clause }|t], acc, old) do
stab = { :->, [], [falsy_clause(old, acc), truthy_clause(new, clause)] }
acc = quote do
case unquote(condition), do: unquote(stab)
end
defp build_cond_clauses([{ :->, new, [[condition], clause] }|t], acc, old) do
clauses = [falsy_clause(old, acc), truthy_clause(new, clause)]
acc = quote do: (case unquote(condition), do: unquote(clauses))
build_cond_clauses(t, acc, new)
end
defp build_cond_clauses([], acc, _), do: acc
defp falsy_clause(meta, acc) do
{ [quote(do: unquote(cond_var) when unquote(cond_var) in [false, nil])], meta, acc }
{ :->, meta, [[quote(do: unquote(cond_var) when unquote(cond_var) in [false, nil])], acc] }
end
defp truthy_clause(meta, clause) do
{ [quote(do: _)], meta, clause }
{ :->, meta, [[quote(do: _)], clause] }
end
# Setting cond: true in metadata turns on a small optimization
+3 -7
View File
@@ -552,11 +552,11 @@ defmodule Kernel.Typespec do
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :->, [line: line], [{args, [line: line], typespec_to_ast(result)}] }
[{ :->, [line: line], [args, typespec_to_ast(result)] }]
end
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
{ :->, [line: line], [{[typespec_to_ast(args)], [line: line], typespec_to_ast(result)}] }
[{ :->, [line: line], [[typespec_to_ast(args)], typespec_to_ast(result)] }]
end
defp typespec_to_ast({ :type, line, :fun, [] }) do
@@ -669,11 +669,7 @@ defmodule Kernel.Typespec do
end
# Handle funs
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
typespec({:->, meta, [{arguments, cmeta, return}]}, vars, caller)
end
defp typespec({:->, meta, [{arguments, _, return}]}, vars, caller) when is_list(arguments) do
defp typespec([{:->, meta, [arguments, return]}], vars, caller) when is_list(arguments) do
args = fn_args(meta, arguments, return, vars, caller)
{ :type, line(meta), :fun, args }
end
+16 -11
View File
@@ -5,8 +5,11 @@ defmodule Macro do
Conveniences for working with macros.
"""
@typedoc "Abstract Syntax Tree (AST) node"
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
@typedoc "Abstract Syntax Tree (AST)"
@type t :: node | { t, t } | atom | number | binary | [t]
@typedoc "The AST node (remaining ones are literals)"
@type node :: { node | atom, Keyword.t, atom | [t] }
@binary_ops [ :===, :!==,
:==, :!=, :<=, :>=,
@@ -309,22 +312,22 @@ defmodule Macro do
end
# Fn keyword
def to_string({ :fn, _, [{ :->, _, [{_, _, tuple}] } = arrow] } = ast, fun)
def to_string({ :fn, _, [{ :->, _, [_, tuple] }] = arrow } = ast, fun)
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
end
def to_string({ :fn, _, [{ :->, _, [_] } = block] } = ast, fun) do
def to_string({ :fn, _, [{ :->, _, _ }] = block } = ast, fun) do
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
end
def to_string({ :fn, _, [block] } = ast, fun) do
def to_string({ :fn, _, block } = ast, fun) do
block = adjust_new_lines block_to_string(block, fun), "\n "
fun.(ast, "fn\n " <> block <> "\nend")
end
# left -> right
def to_string({ :->, _, _ } = ast, fun) do
def to_string([{ :->, _, _ }|_] = ast, fun) do
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
end
@@ -442,8 +445,8 @@ defmodule Macro do
atom_to_binary(key) <> "\n " <> block <> "\n"
end
defp block_to_string({ :->, _, exprs }, fun) do
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
defp block_to_string([{ :->, _, _ }|_] = block, fun) do
Enum.map_join(block, "\n", fn({ :->, _, [left, right] }) ->
left = comma_join_or_empty_paren(left, fun, false)
left <> "->\n " <> adjust_new_lines block_to_string(right, fun), "\n "
end)
@@ -483,8 +486,8 @@ defmodule Macro do
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
defp arrow_to_string({ :->, _, pairs }, fun, paren // false) do
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
defp arrow_to_string(pairs, fun, paren // false) do
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
left = comma_join_or_empty_paren(left, fun, paren)
left <> "-> " <> to_string(right, fun)
end)
@@ -707,7 +710,9 @@ defmodule Macro do
{ tree, cache }
end
@doc false # Used internally by Elixir
# TODO: Rewrite this function to use elixir_exp
# For such, there is no need for a cache.
@doc false
def expand_all(tree, env, cache) do
expand_all_until(expand(tree, env, cache), env)
end
+11 -3
View File
@@ -13,16 +13,24 @@ get_pairs(Meta, Key, Clauses, S) ->
get_pairs(Meta, Key, Clauses, S, AllowNil) ->
case lists:keyfind(Key, 1, Clauses) of
{ Key, { '->', _, Pairs } } ->
[{ Key, PMeta, Left, Right } || { Left, PMeta, Right } <- Pairs];
{ Key, Pairs } when is_list(Pairs) ->
[get_pair(Meta, Key, Pair, S) || Pair <- Pairs];
{ Key, nil } when AllowNil ->
[];
{ Key, _ } ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~ts", [Key]);
get_pairs_error(Meta, Key, S);
_ ->
[]
end.
get_pair(_Meta, Key, { '->', Meta, [Left, Right] }, _S) ->
{ Key, Meta, Left, Right };
get_pair(Meta, Key, _Other, S) ->
get_pairs_error(Meta, Key, S).
get_pairs_error(Meta, Key, S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "expected -> clauses for key ~ts", [Key]).
% Function for translating assigns.
assigns(Fun, Args, #elixir_scope{context=Context, temp_vars=TempVars,
+4
View File
@@ -203,6 +203,10 @@ expand({ quote, Meta, [_, _] }, E) ->
%% Functions
%% TODO: Remove me. Temporary during refactoring.
expand({ '&', _, [Arg] } = Original, E) when is_integer(Arg) ->
{ Original, E };
expand({ '&', Meta, [Arg] }, E) ->
% assert_no_match_or_guard_scope(Meta, '&', S),
case elixir_fn:capture(Meta, Arg, E) of
+4 -2
View File
@@ -5,7 +5,7 @@
-include("elixir.hrl").
fn(Meta, Clauses, S) ->
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
Transformer = fun({ '->', CMeta, [ArgsWithGuards, Expr] }, Acc) ->
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
elixir_clauses:assigns_block(?line(CMeta), fun translate_fn_match/2, Args, [Expr], Guards, umergec(S, Acc))
end,
@@ -25,6 +25,8 @@ translate_fn_match(Arg, S) ->
{ TArg, TS } = elixir_translator:translate(Arg, S#elixir_scope{extra=fn_match}),
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
%% Capture
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, E) when is_atom(F), is_integer(A) ->
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
capture_require(Meta, { Dot, RequireMeta, Args }, E, true);
@@ -97,7 +99,7 @@ do_capture(Meta, Expr, E, Sequential) ->
invalid_capture(Meta, Expr, E);
{ EExpr, EDict } ->
EVars = validate(Meta, EDict, 1, E),
Fn = { fn, Meta, [{ '->', [], [{ EVars, Meta, EExpr }]}]},
Fn = { fn, Meta, [{ '->', Meta, [EVars, EExpr]}]},
{ expanded, Fn, E#elixir_env{macro_counter=E#elixir_env.macro_counter+1} }
end.
+4 -4
View File
@@ -578,7 +578,7 @@ extract_identifier(Other) -> Other.
%% Fn
build_fn(Op, Stab) ->
{ fn, meta(Op), [Stab] }.
{ fn, meta(Op), Stab }.
%% Access
@@ -632,20 +632,20 @@ string_tokens_parse(Line, Tokens) ->
%% Keywords
build_stab([{ '->', Meta, [Left, Right] }|T]) ->
{ '->', Meta, build_stab(Meta, T, Left, [Right], []) };
build_stab(Meta, T, Left, [Right], []);
build_stab(Else) ->
build_block(Else).
build_stab(Old, [{ '->', New, [Left, Right] }|T], Marker, Temp, Acc) ->
H = { Marker, Old, build_block(lists:reverse(Temp)) },
H = { '->', Old, [Marker, build_block(lists:reverse(Temp))] },
build_stab(New, T, Left, [Right], [H|Acc]);
build_stab(Meta, [H|T], Marker, Temp, Acc) ->
build_stab(Meta, T, Marker, [H|Temp], Acc);
build_stab(Meta, [], Marker, Temp, Acc) ->
H = { Marker, Meta, build_block(lists:reverse(Temp)) },
H = { '->', Meta, [Marker, build_block(lists:reverse(Temp))] },
lists:reverse([H|Acc]).
%% Every time the parser sees a (unquote_splicing())
+2 -2
View File
@@ -243,9 +243,9 @@ translate_each({ '&', Meta, [Arg] }, S) ->
translate_each(TE, S#elixir_scope{macro_counter=MacroCounter})
end;
translate_each({ fn, Meta, [{ '->', _, Pairs }] }, S) ->
translate_each({ fn, Meta, Clauses }, S) ->
assert_no_match_or_guard_scope(Meta, 'fn', S),
elixir_fn:fn(Meta, Pairs, S);
elixir_fn:fn(Meta, Clauses, S);
%% Case
+9 -9
View File
@@ -112,22 +112,22 @@ defmodule Kernel.QuoteTest do
end
test :when do
assert {:->,_,[{[{:when,_,[1,2,3,4]}],_,5}]} = quote(do: (1, 2, 3 when 4 -> 5))
assert {:->,_,[{[{:when,_,[1,2,3,4]}],_,5}]} = quote(do: ((1, 2, 3) when 4 -> 5))
assert [{:->,_,[[{:when,_,[1,2,3,4]}],5]}] = quote(do: (1, 2, 3 when 4 -> 5))
assert [{:->,_,[[{:when,_,[1,2,3,4]}],5]}] = quote(do: ((1, 2, 3) when 4 -> 5))
assert {:->,_,[{[{:when,_,[1,2,3,{:when,_,[4,5]}]}],_,6}]} =
assert [{:->,_,[[{:when,_,[1,2,3,{:when,_,[4,5]}]}],6]}] =
quote(do: ((1, 2, 3) when 4 when 5 -> 6))
end
test :stab do
assert { :->, _, [{[], _, _}] } = (quote do -> end)
assert { :->, _, [{[], _, _}] } = (quote do: (->))
assert [{ :->, _, [[], nil] }] = (quote do -> end)
assert [{ :->, _, [[], nil] }] = (quote do: (->))
assert { :->, _, [{[1], _, _}] } = (quote do 1 -> end)
assert { :->, _, [{[1], _, _}] } = (quote do: (1 ->))
assert [{ :->, _, [[1], nil] }] = (quote do 1 -> end)
assert [{ :->, _, [[1], nil] }] = (quote do: (1 ->))
assert { :->, _, [{[], _, 1}] } = (quote do -> 1 end)
assert { :->, _, [{[], _, 1}] } = (quote do: (-> 1))
assert [{ :->, _, [[], 1] }] = (quote do -> 1 end)
assert [{ :->, _, [[], 1] }] = (quote do: (-> 1))
end
test :bind_quoted do