Nonterminals
  grammar expr_list
  expr container_expr block_expr access_expr
  no_parens_expr no_parens_zero_expr no_parens_one_expr no_parens_one_ambig_expr
  bracket_expr bracket_at_expr bracket_arg matched_expr unmatched_expr
  unmatched_op_expr matched_op_expr no_parens_op_expr no_parens_many_expr
  comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol capture_op_eol
  dual_op_eol mult_op_eol concat_op_eol xor_op_eol pipe_op_eol stab_op_eol
  arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
  type_op_eol rel_op_eol ternary_op_eol
  open_paren close_paren empty_paren eoe
  list list_args open_bracket close_bracket
  tuple open_curly close_curly
  bit_string open_bit close_bit
  map map_op map_close map_args struct_expr struct_op
  assoc_op_eol assoc_expr assoc_base assoc_update assoc_update_kw assoc
  container_args_base container_args
  call_args_parens_expr call_args_parens_base call_args_parens parens_call
  call_args_no_parens_one call_args_no_parens_ambig call_args_no_parens_expr
  call_args_no_parens_comma_expr call_args_no_parens_all call_args_no_parens_many
  call_args_no_parens_many_strict
  stab stab_eoe stab_expr stab_op_eol_and_expr stab_parens_many
  kw_eol kw_base kw_data kw_call call_args_no_parens_kw_expr call_args_no_parens_kw
  dot_op dot_alias dot_bracket_identifier dot_call_identifier
  dot_identifier dot_op_identifier dot_do_identifier dot_paren_identifier
  do_block fn_eoe do_eoe end_eoe block_eoe block_item block_list
  number
  .

Terminals
  identifier kw_identifier kw_identifier_safe kw_identifier_unsafe bracket_identifier
  paren_identifier do_identifier block_identifier
  fn 'end' alias
  atom atom_quoted atom_safe atom_unsafe bin_string list_string sigil
  bin_heredoc list_heredoc
  dot_call_op op_identifier
  comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
  type_op dual_op mult_op concat_op xor_op pipe_op stab_op when_op assoc_op
  capture_op rel_op ternary_op
  'true' 'false' 'nil' 'do' eol ';' ',' '.'
  '(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
  int flt char
  .

Rootsymbol grammar.

%% Two shift/reduce conflicts coming from call_args_parens and
%% one coming from empty_paren on stab.
Expect 3.

%% Changes in ops and precedence should be reflected on:
%%
%%   1. lib/elixir/lib/code/identifier.ex
%%   2. lib/elixir/pages/operators.md
%%   3. lib/iex/lib/iex/evaluator.ex
%%
%% Note though the operator => in practice has lower precedence
%% than all others, its entry in the table is only to support the
%% %{user | foo => bar} syntax.
Left       5 do.
Right     10 stab_op_eol.     %% ->
Left      20 ','.
Left      40 in_match_op_eol. %% <-, \\ (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.    %% =>
Nonassoc  90 capture_op_eol.  %% &
Right    100 match_op_eol.    %% =
Left     120 or_op_eol.       %% ||, |||, or
Left     130 and_op_eol.      %% &&, &&&, and
Left     140 comp_op_eol.     %% ==, !=, =~, ===, !==
Left     150 rel_op_eol.      %% <, >, <=, >=
Left     160 arrow_op_eol.    %% |>, <<<, >>>, <<~, ~>>, <~, ~>, <~>, <|>
Left     170 in_op_eol.       %% in, not in
Left     180 xor_op_eol.      %% ^^^
Right    190 ternary_op_eol.  %% //
Right    200 concat_op_eol.   %% ++, --, +++, ---, <>, ..
Left     210 dual_op_eol.     %% +, -
Left     220 mult_op_eol.     %% *, /
Nonassoc 300 unary_op_eol.    %% +, -, !, ^, not, ~~~
Left     310 dot_call_op.
Left     310 dot_op.          %% .
Nonassoc 320 at_op_eol.       %% @
Nonassoc 330 dot_identifier.

%%% MAIN FLOW OF EXPRESSIONS

grammar -> eoe : {'__block__', meta_from_token('$1'), []}.
grammar -> expr_list : build_block(reverse('$1')).
grammar -> eoe expr_list : build_block(reverse('$2')).
grammar -> expr_list eoe : build_block(reverse('$1')).
grammar -> eoe expr_list eoe : build_block(reverse('$2')).
grammar -> '$empty' : {'__block__', [], []}.

% Note expressions are on reverse order
expr_list -> expr : ['$1'].
expr_list -> expr_list eoe expr : ['$3' | annotate_eoe('$2', '$1')].

expr -> matched_expr : '$1'.
expr -> no_parens_expr : '$1'.
expr -> unmatched_expr : '$1'.

%% In Elixir we have three main call syntaxes: with parentheses,
%% without parentheses and with do blocks. They are represented
%% in the AST as matched, no_parens and unmatched.
%%
%% Calls without parentheses are further divided according to how
%% problematic they are:
%%
%% (a) no_parens_one: a call with one unproblematic argument
%% (for example, `f a` or `f g a` and similar) (includes unary operators)
%%
%% (b) no_parens_many: a call with several arguments (for example, `f a, b`)
%%
%% (c) no_parens_one_ambig: a call with one argument which is
%% itself a no_parens_many or no_parens_one_ambig (for example, `f g a, b`,
%% `f g h a, b` and similar)
%%
%% Note, in particular, that no_parens_one_ambig expressions are
%% ambiguous and are interpreted such that the outer function has
%% arity 1 (for instance, `f g a, b` is interpreted as `f(g(a, b))` rather
%% than `f(g(a), b)`). Hence the name, no_parens_one_ambig.
%%
%% The distinction is required because we can't, for example, have
%% a function call with a do block as argument inside another do
%% block call, unless there are parentheses:
%%
%%   if if true do true else false end do  #=> invalid
%%   if(if true do true else false end) do #=> valid
%%
%% Similarly, it is not possible to nest calls without parentheses
%% if their arity is more than 1:
%%
%%   foo a, bar b, c  #=> invalid
%%   foo(a, bar b, c) #=> invalid
%%   foo bar a, b     #=> valid
%%   foo a, bar(b, c) #=> valid
%%
%% So the different grammar rules need to take into account
%% if calls without parentheses are do blocks in particular
%% segments and act accordingly.
matched_expr -> matched_expr matched_op_expr : build_op('$1', '$2').
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> capture_op_eol matched_expr : build_unary_op('$1', '$2').
matched_expr -> no_parens_one_expr : '$1'.
matched_expr -> no_parens_zero_expr : '$1'.
matched_expr -> access_expr : '$1'.
matched_expr -> access_expr kw_identifier : error_invalid_kw_identifier('$2').

unmatched_expr -> matched_expr unmatched_op_expr : build_op('$1', '$2').
unmatched_expr -> unmatched_expr matched_op_expr : build_op('$1', '$2').
unmatched_expr -> unmatched_expr unmatched_op_expr : build_op('$1', '$2').
%% TODO: this rule should not be here as it allows [foo do end + foo 1, 2]
%% while it should raise. But the parser raises ambiguity errors if we move
%% it to no_parens_op_expr
unmatched_expr -> unmatched_expr no_parens_op_expr : build_op('$1', '$2').
unmatched_expr -> unary_op_eol expr : build_unary_op('$1', '$2').
unmatched_expr -> at_op_eol expr : build_unary_op('$1', '$2').
unmatched_expr -> capture_op_eol expr : build_unary_op('$1', '$2').
unmatched_expr -> block_expr : '$1'.

no_parens_expr -> matched_expr no_parens_op_expr : build_op('$1', '$2').
no_parens_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2').
no_parens_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2').
no_parens_expr -> capture_op_eol no_parens_expr : build_unary_op('$1', '$2').
no_parens_expr -> no_parens_one_ambig_expr : '$1'.
no_parens_expr -> no_parens_many_expr : '$1'.

block_expr -> dot_call_identifier call_args_parens do_block : build_parens('$1', '$2', '$3').
block_expr -> dot_call_identifier call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3', '$4').
block_expr -> dot_do_identifier do_block : build_no_parens_do_block('$1', [], '$2').
block_expr -> dot_op_identifier call_args_no_parens_all do_block : build_no_parens_do_block('$1', '$2', '$3').
block_expr -> dot_identifier call_args_no_parens_all do_block : build_no_parens_do_block('$1', '$2', '$3').

matched_op_expr -> match_op_eol matched_expr : {'$1', '$2'}.
matched_op_expr -> dual_op_eol matched_expr : {'$1', '$2'}.
matched_op_expr -> mult_op_eol matched_expr : {'$1', '$2'}.
matched_op_expr -> concat_op_eol matched_expr : {'$1', '$2'}.
matched_op_expr -> ternary_op_eol matched_expr : {'$1', '$2'}.
matched_op_expr -> xor_op_eol matched_expr : {'$1', '$2'}.
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'}.
matched_op_expr -> rel_op_eol matched_expr : {'$1', '$2'}.
matched_op_expr -> arrow_op_eol matched_expr : {'$1', '$2'}.
%% Warn for no parens subset
matched_op_expr -> arrow_op_eol no_parens_one_expr : warn_pipe('$1', '$2'), {'$1', '$2'}.

unmatched_op_expr -> match_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> dual_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> mult_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> concat_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> ternary_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> xor_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> and_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> or_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> in_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> in_match_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> type_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> when_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> pipe_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> comp_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> rel_op_eol unmatched_expr : {'$1', '$2'}.
unmatched_op_expr -> arrow_op_eol unmatched_expr : {'$1', '$2'}.

no_parens_op_expr -> match_op_eol no_parens_expr : {'$1', '$2'}.
no_parens_op_expr -> dual_op_eol no_parens_expr : {'$1', '$2'}.
no_parens_op_expr -> mult_op_eol no_parens_expr : {'$1', '$2'}.
no_parens_op_expr -> concat_op_eol no_parens_expr : {'$1', '$2'}.
no_parens_op_expr -> ternary_op_eol no_parens_expr : {'$1', '$2'}.
no_parens_op_expr -> xor_op_eol no_parens_expr : {'$1', '$2'}.
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 -> when_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 -> rel_op_eol no_parens_expr : {'$1', '$2'}.
no_parens_op_expr -> arrow_op_eol no_parens_expr : {'$1', '$2'}.
%% Warn for no parens subset
no_parens_op_expr -> arrow_op_eol no_parens_one_ambig_expr : warn_pipe('$1', '$2'), {'$1', '$2'}.
no_parens_op_expr -> arrow_op_eol no_parens_many_expr : warn_pipe('$1', '$2'), {'$1', '$2'}.

%% Allow when (and only when) with keywords
no_parens_op_expr -> when_op_eol call_args_no_parens_kw : {'$1', '$2'}.

no_parens_one_ambig_expr -> dot_op_identifier call_args_no_parens_ambig : build_no_parens('$1', '$2').
no_parens_one_ambig_expr -> dot_identifier call_args_no_parens_ambig : build_no_parens('$1', '$2').

no_parens_many_expr -> dot_op_identifier call_args_no_parens_many_strict : build_no_parens('$1', '$2').
no_parens_many_expr -> dot_identifier call_args_no_parens_many_strict : build_no_parens('$1', '$2').

no_parens_one_expr -> dot_op_identifier call_args_no_parens_one : build_no_parens('$1', '$2').
no_parens_one_expr -> dot_identifier call_args_no_parens_one : build_no_parens('$1', '$2').
no_parens_zero_expr -> dot_do_identifier : build_no_parens('$1', nil).
no_parens_zero_expr -> dot_identifier : build_no_parens('$1', nil).

%% From this point on, we just have constructs that can be
%% used with the access syntax. Note that (dot_)identifier
%% is not included in this list simply because the tokenizer
%% marks identifiers followed by brackets as bracket_identifier.
access_expr -> bracket_at_expr : '$1'.
access_expr -> bracket_expr : '$1'.
access_expr -> capture_op_eol int : build_unary_op('$1', number_value('$2')).
access_expr -> fn_eoe stab end_eoe : build_fn('$1', '$2', '$3').
access_expr -> open_paren stab close_paren : build_stab('$1', '$2', '$3').
access_expr -> open_paren stab ';' close_paren : build_stab('$1', '$2', '$4').
access_expr -> open_paren ';' stab ';' close_paren : build_stab('$1', '$3', '$5').
access_expr -> open_paren ';' stab close_paren : build_stab('$1', '$3', '$4').
access_expr -> open_paren ';' close_paren : build_stab('$1', [], '$3').
access_expr -> empty_paren : warn_empty_paren('$1'), {'__block__', [], []}.
access_expr -> number : '$1'.
access_expr -> list : element(1, '$1').
access_expr -> map : '$1'.
access_expr -> tuple : '$1'.
access_expr -> 'true' : handle_literal(?id('$1'), '$1').
access_expr -> 'false' : handle_literal(?id('$1'), '$1').
access_expr -> 'nil' : handle_literal(?id('$1'), '$1').
access_expr -> bin_string : build_bin_string('$1', delimiter(<<$">>)).
access_expr -> list_string : build_list_string('$1', delimiter(<<$'>>)).
access_expr -> bin_heredoc : build_bin_heredoc('$1').
access_expr -> list_heredoc : build_list_heredoc('$1').
access_expr -> bit_string : '$1'.
access_expr -> sigil : build_sigil('$1').
access_expr -> atom : handle_literal(?exprs('$1'), '$1').
access_expr -> atom_quoted : handle_literal(?exprs('$1'), '$1', delimiter(<<$">>)).
access_expr -> atom_safe : build_quoted_atom('$1', true, delimiter(<<$">>)).
access_expr -> atom_unsafe : build_quoted_atom('$1', false, delimiter(<<$">>)).
access_expr -> dot_alias : '$1'.
access_expr -> parens_call : '$1'.

number -> int : handle_number(number_value('$1'), '$1', ?exprs('$1')).
number -> flt : handle_number(number_value('$1'), '$1', ?exprs('$1')).
number -> char : handle_number(?exprs('$1'), '$1', number_value('$1')).

%% Also used by maps and structs
parens_call -> dot_call_identifier call_args_parens : build_parens('$1', '$2', {[], []}).
parens_call -> dot_call_identifier call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3', {[], []}).

bracket_arg -> open_bracket kw_data close_bracket : build_access_arg('$1', '$2', '$3').
bracket_arg -> open_bracket container_expr close_bracket : build_access_arg('$1', '$2', '$3').
bracket_arg -> open_bracket container_expr ',' close_bracket : build_access_arg('$1', '$2', '$4').

bracket_expr -> dot_bracket_identifier bracket_arg : build_access(build_no_parens('$1', nil), '$2').
bracket_expr -> access_expr bracket_arg : build_access('$1', '$2').

bracket_at_expr -> at_op_eol dot_bracket_identifier bracket_arg :
                     build_access(build_unary_op('$1', build_no_parens('$2', nil)), '$3').
bracket_at_expr -> at_op_eol access_expr bracket_arg :
                     build_access(build_unary_op('$1', '$2'), '$3').

%% Blocks

do_block -> do_eoe 'end' :
              {do_end_meta('$1', '$2'), [[{handle_literal(do, '$1'), {'__block__', [], []}}]]}.
do_block -> do_eoe stab end_eoe :
              {do_end_meta('$1', '$3'), [[{handle_literal(do, '$1'), build_stab('$2')}]]}.
do_block -> do_eoe block_list 'end' :
              {do_end_meta('$1', '$3'), [[{handle_literal(do, '$1'), {'__block__', [], []}} | '$2']]}.
do_block -> do_eoe stab_eoe block_list 'end' :
              {do_end_meta('$1', '$4'), [[{handle_literal(do, '$1'), build_stab('$2')} | '$3']]}.

eoe -> eol : '$1'.
eoe -> ';' : '$1'.
eoe -> eol ';' : '$1'.

fn_eoe -> 'fn' : '$1'.
fn_eoe -> 'fn' eoe : next_is_eol('$1', '$2').

do_eoe -> 'do' : '$1'.
do_eoe -> 'do' eoe : '$1'.

end_eoe -> 'end' : '$1'.
end_eoe -> eoe 'end' : '$2'.

block_eoe -> block_identifier : '$1'.
block_eoe -> block_identifier eoe : '$1'.

stab -> stab_expr : ['$1'].
stab -> stab eoe stab_expr : ['$3' | annotate_eoe('$2', '$1')].

stab_eoe -> stab : '$1'.
stab_eoe -> stab eoe : '$1'.

%% Here, `element(1, Token)` is the stab operator,
%% while `element(2, Token)` is the expression.
stab_expr -> expr :
               '$1'.
stab_expr -> stab_op_eol_and_expr :
               build_op([], '$1').
stab_expr -> empty_paren stab_op_eol_and_expr :
               build_op([], '$2').
stab_expr -> empty_paren when_op expr stab_op_eol_and_expr :
               build_op([{'when', meta_from_token('$2'), ['$3']}], '$4').
stab_expr -> call_args_no_parens_all stab_op_eol_and_expr :
               build_op(unwrap_when(unwrap_splice('$1')), '$2').
stab_expr -> stab_parens_many stab_op_eol_and_expr :
               build_op(unwrap_splice('$1'), '$2').
stab_expr -> stab_parens_many when_op expr stab_op_eol_and_expr :
               build_op([{'when', meta_from_token('$2'), unwrap_splice('$1') ++ ['$3']}], '$4').

stab_op_eol_and_expr -> stab_op_eol expr : {'$1', '$2'}.
stab_op_eol_and_expr -> stab_op_eol : warn_empty_stab_clause('$1'), {'$1', handle_literal(nil, '$1')}.

block_item -> block_eoe stab_eoe :
                {handle_literal(?exprs('$1'), '$1'), build_stab('$2')}.
block_item -> block_eoe :
                {handle_literal(?exprs('$1'), '$1'), {'__block__', [], []}}.

block_list -> block_item : ['$1'].
block_list -> block_item block_list : ['$1' | '$2'].

%% Helpers

open_paren -> '('      : '$1'.
open_paren -> '(' eol  : next_is_eol('$1', '$2').
close_paren -> ')'     : '$1'.
close_paren -> eol ')' : '$2'.

empty_paren -> open_paren ')' : '$1'.

open_bracket  -> '['     : '$1'.
open_bracket  -> '[' eol : next_is_eol('$1', '$2').
close_bracket -> ']'     : '$1'.
close_bracket -> eol ']' : '$2'.

open_bit  -> '<<'     : '$1'.
open_bit  -> '<<' eol : next_is_eol('$1', '$2').
close_bit -> '>>'     : '$1'.
close_bit -> eol '>>' : '$2'.

open_curly  -> '{'     : '$1'.
open_curly  -> '{' eol : next_is_eol('$1', '$2').
close_curly -> '}'     : '$1'.
close_curly -> eol '}' : '$2'.

% Operators

unary_op_eol -> unary_op : '$1'.
unary_op_eol -> unary_op eol : '$1'.
unary_op_eol -> dual_op : '$1'.
unary_op_eol -> dual_op eol : '$1'.

capture_op_eol -> capture_op : '$1'.
capture_op_eol -> capture_op eol : '$1'.

at_op_eol -> at_op : '$1'.
at_op_eol -> at_op eol : '$1'.

match_op_eol -> match_op : '$1'.
match_op_eol -> match_op eol : '$1'.

dual_op_eol -> dual_op : '$1'.
dual_op_eol -> dual_op eol : next_is_eol('$1', '$2').

mult_op_eol -> mult_op : '$1'.
mult_op_eol -> mult_op eol : next_is_eol('$1', '$2').

concat_op_eol -> concat_op : '$1'.
concat_op_eol -> concat_op eol : next_is_eol('$1', '$2').

ternary_op_eol -> ternary_op : '$1'.
ternary_op_eol -> ternary_op eol : next_is_eol('$1', '$2').

xor_op_eol -> xor_op : '$1'.
xor_op_eol -> xor_op eol : next_is_eol('$1', '$2').

pipe_op_eol -> pipe_op : '$1'.
pipe_op_eol -> pipe_op eol : next_is_eol('$1', '$2').

and_op_eol -> and_op : '$1'.
and_op_eol -> and_op eol : next_is_eol('$1', '$2').

or_op_eol -> or_op : '$1'.
or_op_eol -> or_op eol : next_is_eol('$1', '$2').

in_op_eol -> in_op : '$1'.
in_op_eol -> in_op eol : next_is_eol('$1', '$2').

in_match_op_eol -> in_match_op : '$1'.
in_match_op_eol -> in_match_op eol : next_is_eol('$1', '$2').

type_op_eol -> type_op : '$1'.
type_op_eol -> type_op eol : next_is_eol('$1', '$2').

when_op_eol -> when_op : '$1'.
when_op_eol -> when_op eol : next_is_eol('$1', '$2').

stab_op_eol -> stab_op : '$1'.
stab_op_eol -> stab_op eol : next_is_eol('$1', '$2').

comp_op_eol -> comp_op : '$1'.
comp_op_eol -> comp_op eol : next_is_eol('$1', '$2').

rel_op_eol -> rel_op : '$1'.
rel_op_eol -> rel_op eol : next_is_eol('$1', '$2').

arrow_op_eol -> arrow_op : '$1'.
arrow_op_eol -> arrow_op eol : next_is_eol('$1', '$2').

% Dot operator

dot_op -> '.' : '$1'.
dot_op -> '.' eol : '$1'.

dot_identifier -> identifier : '$1'.
dot_identifier -> matched_expr dot_op identifier : build_dot('$2', '$1', '$3').

dot_alias -> alias : build_alias('$1').
dot_alias -> matched_expr dot_op alias : build_dot_alias('$2', '$1', '$3').
dot_alias -> matched_expr dot_op open_curly '}' : build_dot_container('$2', '$1', [], []).
dot_alias -> matched_expr dot_op open_curly container_args close_curly : build_dot_container('$2', '$1', '$4', newlines_pair('$3', '$5')).

dot_op_identifier -> op_identifier : '$1'.
dot_op_identifier -> matched_expr dot_op op_identifier : build_dot('$2', '$1', '$3').

dot_do_identifier -> do_identifier : '$1'.
dot_do_identifier -> matched_expr dot_op do_identifier : build_dot('$2', '$1', '$3').

dot_bracket_identifier -> bracket_identifier : '$1'.
dot_bracket_identifier -> matched_expr dot_op bracket_identifier : build_dot('$2', '$1', '$3').

dot_paren_identifier -> paren_identifier : '$1'.
dot_paren_identifier -> matched_expr dot_op paren_identifier : build_dot('$2', '$1', '$3').

dot_call_identifier -> dot_paren_identifier : '$1'.
dot_call_identifier -> matched_expr dot_call_op : {'.', meta_from_token('$2'), ['$1']}. % Fun/local calls

% Function calls with no parentheses

call_args_no_parens_expr -> matched_expr : '$1'.
call_args_no_parens_expr -> no_parens_expr : error_no_parens_many_strict('$1').

call_args_no_parens_comma_expr -> matched_expr ',' call_args_no_parens_expr : ['$3', '$1'].
call_args_no_parens_comma_expr -> call_args_no_parens_comma_expr ',' call_args_no_parens_expr : ['$3' | '$1'].

call_args_no_parens_all -> call_args_no_parens_one : '$1'.
call_args_no_parens_all -> call_args_no_parens_ambig : '$1'.
call_args_no_parens_all -> call_args_no_parens_many : '$1'.

call_args_no_parens_one -> call_args_no_parens_kw : ['$1'].
call_args_no_parens_one -> matched_expr : ['$1'].

call_args_no_parens_ambig -> no_parens_expr : ['$1'].

call_args_no_parens_many -> matched_expr ',' call_args_no_parens_kw : ['$1', '$3'].
call_args_no_parens_many -> call_args_no_parens_comma_expr : reverse('$1').
call_args_no_parens_many -> call_args_no_parens_comma_expr ',' call_args_no_parens_kw : reverse(['$3' | '$1']).

call_args_no_parens_many_strict -> call_args_no_parens_many : '$1'.
call_args_no_parens_many_strict -> open_paren call_args_no_parens_kw close_paren : error_no_parens_strict('$1').
call_args_no_parens_many_strict -> open_paren call_args_no_parens_many close_paren : error_no_parens_strict('$1').

stab_parens_many -> open_paren call_args_no_parens_kw close_paren : ['$2'].
stab_parens_many -> open_paren call_args_no_parens_many close_paren : '$2'.

% Containers

container_expr -> matched_expr : '$1'.
container_expr -> unmatched_expr : '$1'.
container_expr -> no_parens_expr : error_no_parens_container_strict('$1').

container_args_base -> container_expr : ['$1'].
container_args_base -> container_args_base ',' container_expr : ['$3' | '$1'].

container_args -> container_args_base : reverse('$1').
container_args -> container_args_base ',' : reverse('$1').
container_args -> container_args_base ',' kw_call : reverse(['$3' | '$1']).

% Function calls with parentheses

call_args_parens_expr -> matched_expr : '$1'.
call_args_parens_expr -> unmatched_expr : '$1'.
call_args_parens_expr -> no_parens_expr : error_no_parens_many_strict('$1').

call_args_parens_base -> call_args_parens_expr : ['$1'].
call_args_parens_base -> call_args_parens_base ',' call_args_parens_expr : ['$3' | '$1'].

call_args_parens -> open_paren ')' :
                      {newlines_pair('$1', '$2'), []}.
call_args_parens -> open_paren no_parens_expr close_paren :
                      {newlines_pair('$1', '$3'), ['$2']}.
call_args_parens -> open_paren kw_call close_paren :
                      {newlines_pair('$1', '$3'), ['$2']}.
call_args_parens -> open_paren kw_call ',' close_paren :
                      warn_trailing_comma('$3'), {newlines_pair('$1', '$4'), ['$2']}.
call_args_parens -> open_paren call_args_parens_base close_paren :
                      {newlines_pair('$1', '$3'), reverse('$2')}.
call_args_parens -> open_paren call_args_parens_base ',' kw_call close_paren :
                      {newlines_pair('$1', '$5'), reverse(['$4' | '$2'])}.
call_args_parens -> open_paren call_args_parens_base ',' kw_call ',' close_paren :
                      warn_trailing_comma('$5'), {newlines_pair('$1', '$6'), reverse(['$4' | '$2'])}.

% KV

kw_eol -> kw_identifier : handle_literal(?exprs('$1'), '$1', [{format, keyword}]).
kw_eol -> kw_identifier eol : handle_literal(?exprs('$1'), '$1', [{format, keyword}]).
kw_eol -> kw_identifier_safe : build_quoted_atom('$1', true, [{format, keyword}]).
kw_eol -> kw_identifier_safe eol : build_quoted_atom('$1', true, [{format, keyword}]).
kw_eol -> kw_identifier_unsafe : build_quoted_atom('$1', false, [{format, keyword}]).
kw_eol -> kw_identifier_unsafe eol : build_quoted_atom('$1', false, [{format, keyword}]).

kw_base -> kw_eol container_expr : [{'$1', '$2'}].
kw_base -> kw_base ',' kw_eol container_expr : [{'$3', '$4'} | '$1'].

kw_call -> kw_base : reverse('$1').
kw_call -> kw_base ',' : reverse('$1').
kw_call -> kw_base ',' matched_expr : error_bad_keyword_call_follow_up('$2').

kw_data -> kw_base : reverse('$1').
kw_data -> kw_base ',' : reverse('$1').
kw_data -> kw_base ',' matched_expr : error_bad_keyword_data_follow_up('$2').

call_args_no_parens_kw_expr -> kw_eol matched_expr : {'$1', '$2'}.
call_args_no_parens_kw_expr -> kw_eol no_parens_expr : {'$1', '$2'}.

call_args_no_parens_kw -> call_args_no_parens_kw_expr : ['$1'].
call_args_no_parens_kw -> call_args_no_parens_kw_expr ',' call_args_no_parens_kw : ['$1' | '$3'].
call_args_no_parens_kw -> call_args_no_parens_kw_expr ',' matched_expr : error_bad_keyword_call_follow_up('$2').

% Lists

list_args -> kw_data : '$1'.
list_args -> container_args_base : reverse('$1').
list_args -> container_args_base ',' : reverse('$1').
list_args -> container_args_base ',' kw_data : reverse('$1', '$3').

list -> open_bracket ']' : build_list('$1', [], '$2').
list -> open_bracket list_args close_bracket : build_list('$1', '$2', '$3').

% Tuple

tuple -> open_curly '}' : build_tuple('$1', [], '$2').
tuple -> open_curly container_args close_curly :  build_tuple('$1', '$2', '$3').

% Bitstrings

bit_string -> open_bit '>>' : build_bit('$1', [], '$2').
bit_string -> open_bit container_args close_bit : build_bit('$1', '$2', '$3').

% Map and structs

assoc_op_eol -> assoc_op : '$1'.
assoc_op_eol -> assoc_op eol : '$1'.

assoc_expr -> matched_expr assoc_op_eol matched_expr : {'$1', '$3'}.
assoc_expr -> unmatched_expr assoc_op_eol unmatched_expr : {'$1', '$3'}.
assoc_expr -> matched_expr assoc_op_eol unmatched_expr : {'$1', '$3'}.
assoc_expr -> unmatched_expr assoc_op_eol matched_expr : {'$1', '$3'}.
assoc_expr -> dot_identifier : build_identifier('$1', nil).
assoc_expr -> no_parens_one_expr : '$1'.
assoc_expr -> parens_call : '$1'.

assoc_update -> matched_expr pipe_op_eol assoc_expr : {'$2', '$1', ['$3']}.
assoc_update -> unmatched_expr pipe_op_eol assoc_expr : {'$2', '$1', ['$3']}.

assoc_update_kw -> matched_expr pipe_op_eol kw_data : {'$2', '$1', '$3'}.
assoc_update_kw -> unmatched_expr pipe_op_eol kw_data : {'$2', '$1', '$3'}.

assoc_base -> assoc_expr : ['$1'].
assoc_base -> assoc_base ',' assoc_expr : ['$3' | '$1'].

assoc -> assoc_base : reverse('$1').
assoc -> assoc_base ',' : reverse('$1').

map_op -> '%{}' : '$1'.
map_op -> '%{}' eol : '$1'.

map_close -> kw_data close_curly : {'$1', '$2'}.
map_close -> assoc close_curly : {'$1', '$2'}.
map_close -> assoc_base ',' kw_data close_curly : {reverse('$1', '$3'), '$4'}.

map_args -> open_curly '}' : build_map('$1', [], '$2').
map_args -> open_curly map_close : build_map('$1', element(1, '$2'), element(2, '$2')).
map_args -> open_curly assoc_update close_curly : build_map_update('$1', '$2', '$3', []).
map_args -> open_curly assoc_update ',' close_curly : build_map_update('$1', '$2', '$4', []).
map_args -> open_curly assoc_update ',' map_close : build_map_update('$1', '$2', element(2, '$4'), element(1, '$4')).
map_args -> open_curly assoc_update_kw close_curly : build_map_update('$1', '$2', '$3', []).

struct_op -> '%' : '$1'.
struct_expr -> atom : handle_literal(?exprs('$1'), '$1', []).
struct_expr -> atom_quoted : handle_literal(?exprs('$1'), '$1', delimiter(<<$">>)).
struct_expr -> dot_alias : '$1'.
struct_expr -> dot_identifier : build_identifier('$1', nil).
struct_expr -> at_op_eol struct_expr : build_unary_op('$1', '$2').
struct_expr -> unary_op_eol struct_expr : build_unary_op('$1', '$2').
struct_expr -> parens_call : '$1'.

map -> map_op map_args : '$2'.
map -> struct_op struct_expr map_args : {'%', meta_from_token('$1'), ['$2', '$3']}.
map -> struct_op struct_expr eol map_args : {'%', meta_from_token('$1'), ['$2', '$4']}.

Erlang code.

-define(file(), get(elixir_parser_file)).
-define(columns(), get(elixir_parser_columns)).
-define(token_metadata(), get(elixir_token_metadata)).

-define(id(Token), element(1, Token)).
-define(location(Token), element(2, Token)).
-define(exprs(Token), element(3, Token)).
-define(meta(Node), element(2, Node)).
-define(rearrange_uop(Op), (Op == 'not' orelse Op == '!')).

%% The following directive is needed for (significantly) faster
%% compilation of the generated .erl file by the HiPE compiler
-compile([{hipe, [{regalloc, linear_scan}]}]).
-compile({inline, meta_from_token/1, meta_from_location/1, line_from_location/1, is_eol/1}).
-import(lists, [reverse/1, reverse/2]).

meta_from_token(Token) ->
  meta_from_location(?location(Token)).

meta_from_location({Line, Column, _}) ->
  case ?columns() of
    true -> [{line, Line}, {column, Column}];
    false -> [{line, Line}]
  end.

line_from_location({Line, _Column, _}) -> Line.

do_end_meta(Do, End) ->
  case ?token_metadata() of
    true ->
      [{do, meta_from_location(?location(Do))}, {'end', meta_from_location(?location(End))}];
    false ->
      []
  end.

meta_from_token_with_closing(Begin, End) ->
  case ?token_metadata() of
    true ->
      [{closing, meta_from_location(?location(End))} | meta_from_token(Begin)];
    false ->
      meta_from_token(Begin)
  end.

append_non_empty(Left, []) -> Left;
append_non_empty(Left, Right) -> Left ++ Right.

%% Handle metadata in literals

handle_literal(Literal, Token) ->
  handle_literal(Literal, Token, []).

handle_literal(Literal, Token, ExtraMeta) ->
  case get(elixir_literal_encoder) of
    false ->
      Literal;

    Fun ->
      Meta = ExtraMeta ++ meta_from_token(Token),
      case Fun(Literal, Meta) of
        {ok, EncodedLiteral} ->
          EncodedLiteral;
        {error, Reason} ->
          return_error(Meta, elixir_utils:characters_to_list(Reason) ++ [": "], "literal")
      end
  end.

handle_number(Number, Token, Original) ->
  case ?token_metadata() of
    true -> handle_literal(Number, Token, [{token, elixir_utils:characters_to_binary(Original)}]);
    false -> handle_literal(Number, Token, [])
  end.

number_value({_, {_, _, Value}, _}) ->
  Value.

%% Operators

build_op(Left, {Op, Right}) ->
  build_op(Left, Op, Right).

build_op(AST, {_Kind, Location, '//'}, Right) ->
  case AST of
    {'..', Meta, [Left, Middle]} ->
      {'..//', Meta, [Left, Middle, Right]};

    _ ->
      return_error(meta_from_location(Location), "the range step operator (//) must immediately follow the range definition operator (..), for example: 1..9//2. If you wanted to define a default argument, use (\\\\) instead. Syntax error before: ", "'//'")
  end;

build_op({UOp, _, [Left]}, {_Kind, Location, 'in'}, Right) when ?rearrange_uop(UOp) ->
  %% TODO: Remove "not left in right" rearrangement on v2.0
  elixir_errors:erl_warn(line_from_location(Location), ?file(),
    "\"not expr1 in expr2\" is deprecated. "
    "Instead use \"expr1 not in expr2\" if you require Elixir v1.5+, "
    "or \"not(expr1 in expr2)\" if you have to support earlier Elixir versions"),
  {UOp, meta_from_location(Location), [{'in', meta_from_location(Location), [Left, Right]}]};

build_op(Left, {_Kind, Location, 'not in'}, Right) ->
  Meta = meta_from_location(Location),
  {'not', Meta, [{'in', Meta, [Left, Right]}]};

build_op(Left, {_Kind, Location, Op}, Right) ->
  {Op, newlines_op(Location) ++ meta_from_location(Location), [Left, Right]}.

build_unary_op({_Kind, Location, Op}, Expr) ->
  {Op, meta_from_location(Location), [Expr]}.

build_list(Left, Args, Right) ->
  {handle_literal(Args, Left, newlines_pair(Left, Right)), ?location(Left)}.

build_tuple(Left, [Arg1, Arg2], Right) ->
  handle_literal({Arg1, Arg2}, Left, newlines_pair(Left, Right));
build_tuple(Left, Args, Right) ->
  {'{}', newlines_pair(Left, Right) ++ meta_from_token(Left), Args}.

build_bit(Left, Args, Right) ->
  {'<<>>', newlines_pair(Left, Right) ++ meta_from_token(Left), Args}.

build_map(Left, Args, Right) ->
  {'%{}', newlines_pair(Left, Right) ++ meta_from_token(Left), Args}.

build_map_update(Left, {Pipe, Struct, Map}, Right, Extra) ->
  Op = build_op(Struct, Pipe, append_non_empty(Map, Extra)),
  {'%{}', newlines_pair(Left, Right) ++ meta_from_token(Left), [Op]}.

%% Blocks

build_block([{Op, _, [_]}]=Exprs) when ?rearrange_uop(Op) ->
  {'__block__', [], Exprs};
build_block([{unquote_splicing, _, [_]}]=Exprs) ->
  {'__block__', [], Exprs};
build_block([Expr]) ->
  Expr;
build_block(Exprs) ->
  {'__block__', [], Exprs}.

%% Newlines

newlines_pair(Left, Right) ->
  case ?token_metadata() of
    true ->
      newlines(?location(Left), [{closing, meta_from_location(?location(Right))}]);
    false ->
      []
  end.

newlines_op(Location) ->
  case ?token_metadata() of
    true -> newlines(Location, []);
    false -> []
  end.

next_is_eol(Token, {_, {_, _, Count}}) ->
  {Line, Column, _} = ?location(Token),
  setelement(2, Token, {Line, Column, Count}).

newlines({_, _, Count}, Meta) when is_integer(Count) and (Count > 0) ->
  [{newlines, Count} | Meta];
newlines(_, Meta) ->
  Meta.

annotate_eoe(Token, Stack) ->
  case ?token_metadata() of
    true ->
      case {Token, Stack} of
        {{_, Location}, [{'->', StabMeta, [StabArgs, {Left, Meta, Right}]} | Rest]} when is_list(Meta) ->
          [{'->', StabMeta, [StabArgs, {Left, [{end_of_expression, end_of_expression(Location)} | Meta], Right}]} | Rest];

        {{_, Location}, [{Left, Meta, Right} | Rest]} when is_list(Meta) ->
          [{Left, [{end_of_expression, end_of_expression(Location)} | Meta], Right} | Rest];

        _ ->
          Stack
      end;
    false ->
      Stack
  end.

end_of_expression({_, _, Count} = Location) when is_integer(Count) ->
  [{newlines, Count} | meta_from_location(Location)];
end_of_expression(Location) ->
  meta_from_location(Location).

%% Dots

build_alias({'alias', Location, Alias}) ->
  {'__aliases__', meta_from_location(Location), [Alias]}.

build_dot_alias(_Dot, {'__aliases__', Meta, Left}, {'alias', _, Right}) ->
  {'__aliases__', Meta, Left ++ [Right]};
build_dot_alias(_Dot, Atom, Right) when is_atom(Atom) ->
  error_bad_atom(Right);
build_dot_alias(Dot, Expr, {'alias', _, Right}) ->
  {'__aliases__', meta_from_token(Dot), [Expr, Right]}.

build_dot_container(Dot, Left, Right, Extra) ->
  Meta = meta_from_token(Dot),
  {{'.', Meta, [Left, '{}']}, Extra ++ Meta, Right}.

build_dot(Dot, Left, Right) ->
  {'.', meta_from_token(Dot), [Left, extract_identifier(Right)]}.

extract_identifier({Kind, _, Identifier}) when
    Kind == identifier; Kind == bracket_identifier; Kind == paren_identifier;
    Kind == do_identifier; Kind == op_identifier ->
  Identifier.

%% Identifiers

build_nested_parens(Dot, Args1, {Args2Meta, Args2}, {BlockMeta, Block}) ->
  Identifier = build_parens(Dot, Args1, {[], []}),
  Meta = BlockMeta ++ Args2Meta ++ ?meta(Identifier),
  {Identifier, Meta, append_non_empty(Args2, Block)}.

build_parens(Expr, {ArgsMeta, Args}, {BlockMeta, Block}) ->
  {BuiltExpr, BuiltMeta, BuiltArgs} = build_identifier(Expr, append_non_empty(Args, Block)),
  {BuiltExpr, BlockMeta ++ ArgsMeta ++ BuiltMeta, BuiltArgs}.

build_no_parens_do_block(Expr, Args, {BlockMeta, Block}) ->
  {BuiltExpr, BuiltMeta, BuiltArgs} = build_no_parens(Expr, Args ++ Block),
  {BuiltExpr, BlockMeta ++ BuiltMeta, BuiltArgs}.

build_no_parens(Expr, Args) ->
  build_identifier(Expr, Args).

build_identifier({'.', Meta, _} = Dot, nil) ->
  {Dot, [{no_parens, true} | Meta], []};

build_identifier({'.', Meta, _} = Dot, Args) ->
  {Dot, Meta, Args};

build_identifier({op_identifier, Location, Identifier}, [Arg]) ->
  {Identifier, [{ambiguous_op, nil} | meta_from_location(Location)], [Arg]};

build_identifier({_, Location, Identifier}, Args) ->
  {Identifier, meta_from_location(Location), Args}.

%% Fn

build_fn(Fn, Stab, End) ->
  case check_stab(Stab, none) of
    stab ->
      Meta = newlines_op(?location(Fn)) ++ meta_from_token_with_closing(Fn, End),
      {fn, Meta, collect_stab(Stab, [], [])};
    block ->
      return_error(meta_from_token(Fn), "expected anonymous functions to be defined with -> inside: ", "'fn'")
  end.

%% Access

build_access_arg(Left, Args, Right) ->
  {Args, newlines_pair(Left, Right) ++ meta_from_token(Left)}.

build_access(Expr, {List, Meta}) ->
  {{'.', Meta, ['Elixir.Access', get]}, Meta, [Expr, List]}.

%% Interpolation aware

build_sigil({sigil, Location, Sigil, Parts, Modifiers, Indentation, Delimiter}) ->
  Meta = meta_from_location(Location),
  MetaWithDelimiter = [{delimiter, Delimiter} | Meta],
  MetaWithIndentation = meta_with_indentation(Meta, Indentation),
  {list_to_atom("sigil_" ++ [Sigil]),
   MetaWithDelimiter,
   [{'<<>>', MetaWithIndentation, string_parts(Parts)}, Modifiers]}.

meta_with_indentation(Meta, nil) ->
  Meta;
meta_with_indentation(Meta, Indentation) ->
  [{indentation, Indentation} | Meta].

build_bin_heredoc({bin_heredoc, Location, Args}) ->
  build_bin_string({bin_string, Location, Args}, delimiter(<<$", $", $">>)).

build_list_heredoc({list_heredoc, Location, Args}) ->
  build_list_string({list_string, Location, Args}, delimiter(<<$', $', $'>>)).

build_bin_string({bin_string, _Location, [H]} = Token, ExtraMeta) when is_binary(H) ->
  handle_literal(H, Token, ExtraMeta);
build_bin_string({bin_string, Location, Args}, ExtraMeta) ->
  Meta =
    case ?token_metadata() of
      true -> ExtraMeta ++ meta_from_location(Location);
      false -> meta_from_location(Location)
    end,
  {'<<>>', Meta, string_parts(Args)}.

build_list_string({list_string, _Location, [H]} = Token, ExtraMeta) when is_binary(H) ->
  handle_literal(elixir_utils:characters_to_list(H), Token, ExtraMeta);
build_list_string({list_string, Location, Args}, ExtraMeta) ->
  Meta = meta_from_location(Location),
  MetaWithExtra =
    case ?token_metadata() of
      true -> ExtraMeta ++ Meta;
      false -> Meta
    end,
  {{'.', Meta, ['Elixir.List', to_charlist]}, MetaWithExtra, [charlist_parts(Args)]}.

build_quoted_atom({_, _Location, [H]} = Token, Safe, ExtraMeta) when is_binary(H) ->
  Op = binary_to_atom_op(Safe),
  handle_literal(erlang:Op(H, utf8), Token, ExtraMeta);
build_quoted_atom({_, Location, Args}, Safe, ExtraMeta) ->
  Meta = meta_from_location(Location),
  MetaWithExtra =
    case ?token_metadata() of
      true -> ExtraMeta ++ Meta;
      false -> Meta
    end,
  {{'.', Meta, [erlang, binary_to_atom_op(Safe)]}, MetaWithExtra, [{'<<>>', Meta, string_parts(Args)}, utf8]}.

binary_to_atom_op(true)  -> binary_to_existing_atom;
binary_to_atom_op(false) -> binary_to_atom.

charlist_parts(Parts) ->
  [charlist_part(Part) || Part <- Parts].
charlist_part(Binary) when is_binary(Binary) ->
  Binary;
charlist_part({Begin, End, Tokens}) ->
  Form = string_tokens_parse(Tokens),
  Meta = meta_from_location(Begin),
  MetaWithExtra =
    case ?token_metadata() of
      true -> [{closing, meta_from_location(End)} | Meta];
      false -> Meta
    end,
  {{'.', Meta, ['Elixir.Kernel', to_string]}, MetaWithExtra, [Form]}.

string_parts(Parts) ->
  [string_part(Part) || Part <- Parts].
string_part(Binary) when is_binary(Binary) ->
  Binary;
string_part({Begin, End, Tokens}) ->
  Form = string_tokens_parse(Tokens),
  Meta = meta_from_location(Begin),
  MetaWithExtra =
    case ?token_metadata() of
      true -> [{closing, meta_from_location(End)} | meta_from_location(Begin)];
      false -> meta_from_location(Begin)
    end,
  {'::', Meta, [{{'.', Meta, ['Elixir.Kernel', to_string]}, MetaWithExtra, [Form]}, {binary, Meta, nil}]}.

string_tokens_parse(Tokens) ->
  case parse(Tokens) of
    {ok, Forms} -> Forms;
    {error, _} = Error -> throw(Error)
  end.

delimiter(Delimiter) ->
  case ?token_metadata() of
    true -> [{delimiter, Delimiter}];
    false -> []
  end.

%% Keywords

check_stab([{'->', _, [_, _]}], _) -> stab;
check_stab([], none) -> block;
check_stab([_], none) -> block;
check_stab([_], Meta) -> error_invalid_stab(Meta);
check_stab([{'->', Meta, [_, _]} | T], _) -> check_stab(T, Meta);
check_stab([_ | T], MaybeMeta) -> check_stab(T, MaybeMeta).

build_stab(Stab) ->
  case check_stab(Stab, none) of
    block -> build_block(reverse(Stab));
    stab -> collect_stab(Stab, [], [])
  end.

build_stab(Before, Stab, After) ->
  case build_stab(Stab) of
    {'__block__', Meta, Block} ->
      {'__block__', Meta ++ meta_from_token_with_closing(Before, After), Block};
    Other ->
      Other
  end.

collect_stab([{'->', Meta, [Left, Right]} | T], Exprs, Stabs) ->
  Stab = {'->', Meta, [Left, build_block([Right | Exprs])]},
  collect_stab(T, [], [Stab | Stabs]);

collect_stab([H | T], Exprs, Stabs) ->
  collect_stab(T, [H | Exprs], Stabs);

collect_stab([], [], Stabs) ->
  Stabs.

%% Every time the parser sees a (unquote_splicing())
%% it assumes that a block is being spliced, wrapping
%% the splicing in a __block__. But in the stab clause,
%% we can have (unquote_splicing(1, 2, 3)) -> :ok, in such
%% case, we don't actually want the block, since it is
%% an arg style call. unwrap_splice unwraps the splice
%% from such blocks.
unwrap_splice([{'__block__', _, [{unquote_splicing, _, _}] = Splice}]) ->
  Splice;
unwrap_splice(Other) ->
  Other.

unwrap_when(Args) ->
  case elixir_utils:split_last(Args) of
    {Start, {'when', Meta, [_, _] = End}} ->
      [{'when', Meta, Start ++ End}];
    {_, _} ->
      Args
  end.

%% Warnings and errors

return_error(Meta, Error, Token) ->
  return_error(Meta, [Error, Token]).

error_invalid_stab(MetaStab) ->
  return_error(MetaStab,
    "unexpected operator ->. If you want to define multiple clauses, the first expression must use ->. "
    "Syntax error before: ", "'->'").

error_bad_atom(Token) ->
  return_error(meta_from_token(Token), "atom cannot be followed by an alias. "
    "If the '.' was meant to be part of the atom's name, "
    "the atom name must be quoted. Syntax error before: ", "'.'").

error_bad_keyword_call_follow_up(Token) ->
  return_error(meta_from_token(Token),
    "unexpected expression after keyword list. Keyword lists must always come as the last argument. Therefore, this is not allowed:\n\n"
    "    function_call(1, some: :option, 2)\n\n"
    "Instead, wrap the keyword in brackets:\n\n"
    "    function_call(1, [some: :option], 2)\n\n"
    "Syntax error after: ", "','").

error_bad_keyword_data_follow_up(Token) ->
  return_error(meta_from_token(Token),
    "unexpected expression after keyword list. Keyword lists must always come last in lists and maps. Therefore, this is not allowed:\n\n"
    "    [some: :value, :another]\n"
    "    %{some: :value, another => value}\n\n"
    "Instead, reorder it to be the last entry:\n\n"
    "    [:another, some: :value]\n"
    "    %{another => value, some: :value}\n\n"
    "Syntax error after: ", "','").

error_no_parens_strict(Token) ->
  return_error(meta_from_token(Token), "unexpected parentheses. If you are making a "
    "function call, do not insert spaces between the function name and the "
    "opening parentheses. Syntax error before: ", "'('").

error_no_parens_many_strict(Node) ->
  return_error(?meta(Node),
    "unexpected comma. Parentheses are required to solve ambiguity in nested calls.\n\n"
    "This error happens when you have nested function calls without parentheses. "
    "For example:\n\n"
    "    one a, two b, c, d\n\n"
    "In the example above, we don't know if the parameters \"c\" and \"d\" apply "
    "to the function \"one\" or \"two\". You can solve this by explicitly adding "
    "parentheses:\n\n"
    "    one a, two(b, c, d)\n\n"
    "Or by adding commas (in case a nested call is not intended):\n\n"
    "    one, a, two, b, c, d\n\n"
    "Elixir cannot compile otherwise. Syntax error before: ", "','").

error_no_parens_container_strict(Node) ->
  return_error(?meta(Node),
    "unexpected comma. Parentheses are required to solve ambiguity inside containers.\n\n"
    "This error may happen when you forget a comma in a list or other container:\n\n"
    "    [a, b c, d]\n\n"
    "Or when you have ambiguous calls:\n\n"
    "    [one, two three, four, five]\n\n"
    "In the example above, we don't know if the parameters \"four\" and \"five\" "
    "belongs to the list or the function \"two\". You can solve this by explicitly "
    "adding parentheses:\n\n"
    "    [one, two(three, four), five]\n\n"
    "Elixir cannot compile otherwise. Syntax error before: ", "','").

error_invalid_kw_identifier({_, _, do} = Token) ->
  return_error(meta_from_token(Token), sourceror_elixir_tokenizer:invalid_do_error("unexpected keyword: "), "do:");
error_invalid_kw_identifier({_, _, KW} = Token) ->
  return_error(meta_from_token(Token), "syntax error before: ", "'" ++ atom_to_list(KW) ++ ":'").

%% TODO: Make this an error on v2.0
warn_empty_paren({_, {Line, _, _}}) ->
  elixir_errors:erl_warn(Line, ?file(),
    "invalid expression (). "
    "If you want to invoke or define a function, make sure there are "
    "no spaces between the function name and its arguments. If you wanted "
    "to pass an empty block or code, pass a value instead, such as a nil or an atom").

%% TODO: Make this an error on v2.0
warn_pipe({arrow_op, {Line, _, _}, Op}, {_, [_ | _], [_ | _]}) ->
  elixir_errors:erl_warn(Line, ?file(),
    io_lib:format(
      "parentheses are required when piping into a function call. For example:\n\n"
      "    foo 1 ~ts bar 2 ~ts baz 3\n\n"
      "is ambiguous and should be written as\n\n"
      "    foo(1) ~ts bar(2) ~ts baz(3)\n\n"
      "Ambiguous pipe found at:",
      [Op, Op, Op, Op]
    )
  );
warn_pipe(_Token, _) ->
  ok.

warn_trailing_comma({',', {Line, _, _}}) ->
  elixir_errors:erl_warn(Line, ?file(),
    "trailing commas are not allowed inside function/macro call arguments"
  ).

warn_empty_stab_clause({stab_op, {Line, _, _}, '->'}) ->
  elixir_errors:erl_warn(Line, ?file(),
    "an expression is always required on the right side of ->. "
    "Please provide a value after ->").