Skip to content

Commit

Permalink
Unify precedence of when, :: and |
Browse files Browse the repository at this point in the history
  • Loading branch information
José Valim committed Mar 31, 2014
1 parent 2de2322 commit d33463d
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 135 deletions.
99 changes: 21 additions & 78 deletions lib/elixir/lib/behaviour.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,56 +46,49 @@ defmodule Behaviour do
@doc """
Define a function callback according to the given type specification.
"""
defmacro defcallback({ :::, _, [fun, return_and_guard] }) do
{ return, guard } = split_return_and_guard(return_and_guard)
do_defcallback(fun, return, guard, __CALLER__)
end

defmacro defcallback(fun) do
do_defcallback(fun, quote(do: term), [], __CALLER__)
defmacro defcallback(spec) do
do_defcallback(split_spec(spec, quote(do: term)), __CALLER__)
end

@doc """
Define a macro callback according to the given type specification.
"""
defmacro defmacrocallback({ :::, _, [fun, return_and_guard] }) do
{ return, guard } = split_return_and_guard(return_and_guard)
do_defmacrocallback(fun, return, guard, __CALLER__)
defmacro defmacrocallback(spec) do
do_defmacrocallback(split_spec(spec, quote(do: Macro.t)), __CALLER__)
end

defmacro defmacrocallback(fun) do
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
defp split_spec({ :when, _, [{ :::, _, [spec, return] }, guard] }, _default) do
{ spec, return, guard }
end

defp split_return_and_guard({ :when, _, [return, guard] }) do
{ return, guard }
defp split_spec({ :when, _, [spec, guard] }, default) do
{ spec, default, guard }
end

defp split_return_and_guard({ :|, meta, [left, right] }) do
{ return, guard } = split_return_and_guard(right)
{ { :|, meta, [left, return] }, guard }
defp split_spec({ :::, _, [spec, return] }, _default) do
{ spec, return, [] }
end

defp split_return_and_guard(other) do
{ other, [] }
defp split_spec(spec, default) do
{ spec, default, [] }
end

defp do_defcallback(fun, return, guards, caller) do
case Macro.decompose_call(fun) do
defp do_defcallback({ spec, return, guards }, caller) do
case Macro.decompose_call(spec) do
{ name, args } ->
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
end
end

defp do_defmacrocallback(fun, return, guards, caller) do
case Macro.decompose_call(fun) do
defp do_defmacrocallback({ spec, return, guards }, caller) do
case Macro.decompose_call(spec) do
{ name, args } ->
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
end
end

Expand All @@ -111,10 +104,9 @@ defmodule Behaviour do
end

quote do
docs_args = unquote(Macro.escape(docs_args))
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
unquote(docs_name), docs_args, unquote(docs_arity))
unquote(docs_name), unquote(docs_arity))
end
end

Expand All @@ -125,61 +117,12 @@ defmodule Behaviour do
defp ensure_not_default(_), do: :ok

@doc false
def store_docs(module, line, kind, name, args, arity) do
def store_docs(module, line, kind, name, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc

signature = Enum.map(Stream.with_index(args), fn { arg, ix } ->
{ simplify_signature(arg, ix + 1), [line: line], nil }
end)

Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, signature, doc }
end

defp simplify_signature({ :::, _, [var, _] }, i) do
simplify_signature(var, i)
end

defp simplify_signature({ :|, _, [first, _] }, i) do
simplify_signature(first, i)
end

defp simplify_signature({ :., _, [_, name] }, _i) do
name
end

defp simplify_signature({ :->, _, list }, i) when is_list(list) do
:"fun#{i}"
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
end

defp simplify_signature({ :.., _, list }, i) when is_list(list) do
:"range#{i}"
end

defp simplify_signature({ :<<>>, _, list }, i) when is_list(list) do
:"bitstring#{i}"
end

defp simplify_signature({ :{}, _, list }, i) when is_list(list) do
:"tuple#{i}"
end

defp simplify_signature({ name, _, args }, _i)
when is_atom(name) and (is_atom(args) or is_list(args)) do
name
end

defp simplify_signature({ ast, _, list }, i) when is_list(list) do
simplify_signature(ast, i)
end

defp simplify_signature(other, i) when is_integer(other), do: :"int#{i}"
defp simplify_signature(other, i) when is_boolean(other), do: :"bool#{i}"
defp simplify_signature(other, i) when is_atom(other), do: :"atom#{i}"
defp simplify_signature(other, i) when is_list(other), do: :"list#{i}"
defp simplify_signature(other, i) when is_tuple(other), do: :"tuple#{i}"
defp simplify_signature(_, i), do: :"arg#{i}"

@doc false
defmacro __using__(_) do
quote do
Expand Down
55 changes: 25 additions & 30 deletions lib/elixir/lib/kernel/typespec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -361,13 +361,13 @@ defmodule Kernel.Typespec do
|> Enum.uniq
|> Enum.map(&{ &1, { :var, meta, nil } })

result = if vars == [] do
typespec_to_ast(result)
spec = { :::, meta, [body, typespec_to_ast(result)] }

if vars == [] do
spec
else
{ :when, meta, [typespec_to_ast(result), vars] }
{ :when, meta, [spec, vars] }
end

{ :::, meta, [body, result] }
end

def spec_to_ast(name, { :type, line, :fun, [] }) do
Expand All @@ -376,23 +376,23 @@ defmodule Kernel.Typespec do

def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
guards =
for {:type, _, :constraint, [{:atom, _, :is_subtype}, [{ :var, _, var }, type]]} <- constraints do
for { :type, _, :constraint, [{ :atom, _, :is_subtype }, [{ :var, _, var }, type]] } <- constraints do
{ var, typespec_to_ast(type) }
end

meta = [line: line]

vars = args ++ [result]
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
|> Kernel.--(Keyword.keys(guards))
|> Enum.map(&{ &1, { :var, meta, nil } })
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
|> Kernel.--(Keyword.keys(guards))
|> Enum.map(&{ &1, { :var, meta, nil } })

args = for arg <- args, do: typespec_to_ast(arg)

{ :::, meta, [
{ name, [line: line], args },
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
{ :when, meta, [
{ :::, meta, [{ name, [line: line], args }, typespec_to_ast(result)] },
guards ++ vars
] }
end

Expand Down Expand Up @@ -542,9 +542,17 @@ defmodule Kernel.Typespec do
end

@doc false
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) when is_atom(name) and name != ::: do

def defspec(type, { :when, _meta, [spec, guard] }, caller) do
defspec(type, spec, guard, caller)
end

def defspec(type, spec, caller) do
defspec(type, spec, [], caller)
end

defp defspec(type, { :::, meta, [{ name, _, args }, return] }, guard, caller) when is_atom(name) and name != ::: do
if is_atom(args), do: args = []
{ return, guard } = split_return_and_guard(return_and_guard)

unless Keyword.keyword?(guard) do
guard = Macro.to_string(guard)
Expand All @@ -564,24 +572,11 @@ defmodule Kernel.Typespec do
code
end

def defspec(_type, other, caller) do
spec = Macro.to_string(other)
defp defspec(_type, spec, _guard, caller) do
spec = Macro.to_string(spec)
compile_error caller, "invalid function type specification: #{spec}"
end

defp split_return_and_guard({ :when, _, [return, guard] }) do
{ return, guard }
end

defp split_return_and_guard({ :|, meta, [left, right] }) do
{ return, guard } = split_return_and_guard(right)
{ { :|, meta, [left, return] }, guard }
end

defp split_return_and_guard(other) do
{ other, [] }
end

defp guard_to_constraints(guard, vars, meta, caller) do
line = line(meta)

Expand Down
21 changes: 14 additions & 7 deletions lib/elixir/src/elixir_parser.yrl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Nonterminals
op_expr matched_op_expr no_parens_op_expr
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol
add_op_eol mult_op_eol exp_op_eol two_op_eol pipe_op_eol stab_op_eol
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol type_op_eol
open_paren close_paren empty_paren
list list_args open_bracket close_bracket
tuple open_curly close_curly
Expand All @@ -28,7 +28,7 @@ Terminals
fn 'end' aliases
number signed_number atom atom_string bin_string list_string sigil
dot_call_op op_identifier
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op type_op
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op
'true' 'false' 'nil' 'do' eol ',' '.'
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
Expand All @@ -45,11 +45,12 @@ Expect 2.
Left 5 do.
Right 10 stab_op_eol. %% ->
Left 20 ','.
Left 40 in_match_op_eol. %% <-, inlist, inbits, \\, :: (allowed in matches along =)
Right 50 pipe_op_eol. %% |
Right 60 assoc_op_eol. %% =>
Right 70 when_op_eol. %% when
Right 80 match_op_eol. %% =
Left 40 in_match_op_eol. %% <-, inlist, inbits, \\ (allowed in matches along =)
Right 50 when_op_eol. %% when
Right 60 type_op_eol. %% ::
Right 70 pipe_op_eol. %% |
Right 80 assoc_op_eol. %% =>
Right 90 match_op_eol. %% =
Left 130 or_op_eol. %% ||, |||, or, xor
Left 140 and_op_eol. %% &&, &&&, and
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
Expand Down Expand Up @@ -138,6 +139,7 @@ op_expr -> and_op_eol expr : { '$1', '$2' }.
op_expr -> or_op_eol expr : { '$1', '$2' }.
op_expr -> in_op_eol expr : { '$1', '$2' }.
op_expr -> in_match_op_eol expr : { '$1', '$2' }.
op_expr -> type_op_eol expr : { '$1', '$2' }.
op_expr -> when_op_eol expr : { '$1', '$2' }.
op_expr -> pipe_op_eol expr : { '$1', '$2' }.
op_expr -> comp_op_eol expr : { '$1', '$2' }.
Expand All @@ -152,6 +154,7 @@ no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> in_match_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> pipe_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> arrow_op_eol no_parens_expr : { '$1', '$2' }.
Expand All @@ -169,6 +172,7 @@ matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> in_match_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> pipe_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
Expand Down Expand Up @@ -323,6 +327,9 @@ in_op_eol -> in_op eol : '$1'.
in_match_op_eol -> in_match_op : '$1'.
in_match_op_eol -> in_match_op eol : '$1'.

type_op_eol -> type_op : '$1'.
type_op_eol -> type_op eol : '$1'.

when_op_eol -> when_op : '$1'.
when_op_eol -> when_op eol : '$1'.

Expand Down
13 changes: 9 additions & 4 deletions lib/elixir/src/elixir_tokenizer.erl
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,14 @@

-define(in_match_op(T1, T2),
T1 == $<, T2 == $-;
T1 == $\\, T2 == $\\;
T1 == $:, T2 == $:).
T1 == $\\, T2 == $\\).

-define(stab_op(T1, T2),
T1 == $-, T2 == $>).

-define(type_op(T1, T2),
T1 == $:, T2 == $:).

-define(pipe_op(T1),
T == $|).

Expand Down Expand Up @@ -278,7 +280,7 @@ tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
% ## Two Token Operators
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);

% ## Single Token Operators
Expand Down Expand Up @@ -364,6 +366,9 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?in_match_op(T1, T2) ->
handle_op(Rest, Line, in_match_op, list_to_atom([T1, T2]), Scope, Tokens);

tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?type_op(T1, T2) ->
handle_op(Rest, Line, type_op, list_to_atom([T1, T2]), Scope, Tokens);

tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
handle_op(Rest, Line, stab_op, list_to_atom([T1, T2]), Scope, Tokens);

Expand Down Expand Up @@ -526,7 +531,7 @@ handle_dot([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
% ## Two Token Operators
handle_dot([$.,T1,T2|Rest], Line, Scope, Tokens) when
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);

% ## Single Token Operators
Expand Down
22 changes: 7 additions & 15 deletions lib/elixir/test/elixir/behaviour_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,14 @@ defmodule BehaviourTest do
test :docs do
docs = Sample.__behaviour__(:docs)
assert [
{{:first, 1}, 10, :def, _, "I should be first."},
{{:foo, 2}, 13, :def, _, "Foo"},
{{:bar, 2}, 16, :def, _, "Bar"},
{{:guarded, 1}, 18, :def, _, nil},
{{:orr, 1}, 20, :def, _, nil},
{{:literal, 5}, 22, :def, _, nil},
{{:last, 1}, 25, :defmacro, _, "I should be last."}
{{:first, 1}, 10, :def, "I should be first."},
{{:foo, 2}, 13, :def, "Foo"},
{{:bar, 2}, 16, :def, "Bar"},
{{:guarded, 1}, 18, :def, nil},
{{:orr, 1}, 20, :def, nil},
{{:literal, 5}, 22, :def, nil},
{{:last, 1}, 25, :defmacro, "I should be last."}
] = docs

assert [{ :integer, _, nil }] = List.keyfind(docs, { :first, 1 }, 0) |> elem(3)
assert [{ :atom, _, nil }, { :binary, _, nil }] = List.keyfind(docs, { :foo, 2 }, 0) |> elem(3)
assert [{ :hello, _, nil }, { :my_var, _, nil }] = List.keyfind(docs, { :bar, 2 }, 0) |> elem(3)
assert [{ :my_var, _, nil }] = List.keyfind(docs, { :guarded, 1 }, 0) |> elem(3)
assert [{ :atom, _, nil }] = List.keyfind(docs, { :orr, 1 }, 0) |> elem(3)
assert [{ :int1, _, nil }, { :tuple2, _, nil }, { :atom3, _, nil }, { :list4, _, nil }, { :bool5, _, nil} ] = List.keyfind(docs, { :literal, 5 }, 0) |> elem(3)
assert [{ :integer, _, nil }] = List.keyfind(docs, { :last, 1 }, 0) |> elem(3)
end

test :callbacks do
Expand Down

0 comments on commit d33463d

Please sign in to comment.