Nonterminals grammar expr_list expr container_expr block_expr access_expr no_parens_expr no_parens_one_expr no_parens_one_ambig_expr bracket_expr bracket_at_expr bracket_arg matched_expr unmatched_expr max_expr 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 add_op_eol mult_op_eol hat_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 type_op_eol rel_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 map_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_maybe_expr stab_parens_many kw_eol kw_base kw call_args_no_parens_kw_expr call_args_no_parens_kw dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier dot_paren_identifier dot_bracket_identifier do_block fn_eoe do_eoe end_eoe block_eoe block_item block_list . Terminals identifier kw_identifier kw_identifier_safe kw_identifier_unsafe bracket_identifier paren_identifier do_identifier block_identifier fn 'end' aliases number signed_number atom atom_safe atom_unsafe 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 type_op dual_op add_op mult_op hat_op two_op pipe_op stab_op when_op assoc_op capture_op rel_op 'true' 'false' 'nil' 'do' eol ';' ',' '.' '(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%' . Rootsymbol grammar. %% Two shift/reduce conflicts coming from call_args_parens. Expect 2. %% Changes in ops and precedence should be reflected on lib/elixir/lib/macro.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 ','. Nonassoc 30 capture_op_eol. %% & 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. %% => Right 90 match_op_eol. %% = Left 130 or_op_eol. %% ||, |||, or Left 140 and_op_eol. %% &&, &&&, and Left 150 comp_op_eol. %% ==, !=, =~, ===, !== Left 160 rel_op_eol. %% <, >, <=, >= Left 170 arrow_op_eol. %% < (op), (op) > (|>, <<<, >>>, ~>>, <<~, ~>, <~, <~>, <|>) Left 180 in_op_eol. %% in Right 200 two_op_eol. %% ++, --, .., <> Left 210 add_op_eol. %% + (op), - (op) Left 220 mult_op_eol. %% * (op), / (op) Left 250 hat_op_eol. %% ^ (op) (^^^) 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 : nil. grammar -> expr_list : to_block('$1'). grammar -> eoe expr_list : to_block('$2'). grammar -> expr_list eoe : to_block('$1'). grammar -> eoe expr_list eoe : to_block('$2'). grammar -> '$empty' : nil. % Note expressions are on reverse order expr_list -> expr : ['$1']. expr_list -> expr_list eoe expr : ['$3'|'$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 %% (e.g. `f a` or `f g a` and similar) %% %% (b) no_parens_many: a call with several arguments (e.g. `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 (e.g. `f g a, b` %% or `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 (e.g. `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(element(1, '$2'), '$1', element(2, '$2')). matched_expr -> matched_expr no_parens_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')). matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2'). matched_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2'). matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2'). matched_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2'). matched_expr -> capture_op_eol matched_expr : build_unary_op('$1', '$2'). matched_expr -> capture_op_eol no_parens_expr : build_unary_op('$1', '$2'). matched_expr -> no_parens_one_expr : '$1'. matched_expr -> access_expr : '$1'. unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')). unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$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'. block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3'). block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4'). block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2'). block_expr -> dot_identifier call_args_no_parens_all do_block : build_identifier('$1', '$2' ++ '$3'). op_expr -> match_op_eol expr : {'$1', '$2'}. op_expr -> add_op_eol expr : {'$1', '$2'}. op_expr -> mult_op_eol expr : {'$1', '$2'}. op_expr -> hat_op_eol expr : {'$1', '$2'}. op_expr -> two_op_eol expr : {'$1', '$2'}. 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'}. op_expr -> rel_op_eol expr : {'$1', '$2'}. op_expr -> arrow_op_eol expr : {'$1', '$2'}. no_parens_op_expr -> match_op_eol no_parens_expr : {'$1', '$2'}. no_parens_op_expr -> add_op_eol no_parens_expr : {'$1', '$2'}. no_parens_op_expr -> mult_op_eol no_parens_expr : {'$1', '$2'}. no_parens_op_expr -> hat_op_eol no_parens_expr : {'$1', '$2'}. no_parens_op_expr -> two_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 -> 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'}. %% Allow when (and only when) with keywords no_parens_op_expr -> when_op_eol no_parens_expr : {'$1', '$2'}. no_parens_op_expr -> when_op_eol call_args_no_parens_kw : {'$1', '$2'}. matched_op_expr -> match_op_eol matched_expr : {'$1', '$2'}. matched_op_expr -> add_op_eol matched_expr : {'$1', '$2'}. matched_op_expr -> mult_op_eol matched_expr : {'$1', '$2'}. matched_op_expr -> hat_op_eol matched_expr : {'$1', '$2'}. matched_op_expr -> two_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'}. no_parens_expr -> no_parens_one_ambig_expr : '$1'. no_parens_expr -> no_parens_many_expr : '$1'. no_parens_one_ambig_expr -> dot_op_identifier call_args_no_parens_ambig : build_identifier('$1', '$2'). no_parens_one_ambig_expr -> dot_identifier call_args_no_parens_ambig : build_identifier('$1', '$2'). no_parens_many_expr -> dot_op_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2'). no_parens_many_expr -> dot_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2'). no_parens_one_expr -> dot_op_identifier call_args_no_parens_one : build_identifier('$1', '$2'). no_parens_one_expr -> dot_identifier call_args_no_parens_one : build_identifier('$1', '$2'). no_parens_one_expr -> dot_do_identifier : build_identifier('$1', nil). no_parens_one_expr -> dot_identifier : build_identifier('$1', nil). %% From this point on, we just have constructs that can be %% used with the access syntax. Notice 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 -> at_op_eol number : build_unary_op('$1', ?exprs('$2')). access_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')). access_expr -> capture_op_eol number : build_unary_op('$1', ?exprs('$2')). access_expr -> fn_eoe stab end_eoe : build_fn('$1', build_stab(reverse('$2'))). access_expr -> open_paren stab close_paren : build_stab(reverse('$2')). access_expr -> open_paren stab ';' close_paren : build_stab(reverse('$2')). access_expr -> open_paren ';' stab ';' close_paren : build_stab(reverse('$3')). access_expr -> open_paren ';' stab close_paren : build_stab(reverse('$3')). access_expr -> open_paren ';' close_paren : build_stab([]). access_expr -> empty_paren : nil. access_expr -> number : ?exprs('$1'). access_expr -> signed_number : {element(4, '$1'), meta('$1'), ?exprs('$1')}. access_expr -> list : element(1, '$1'). access_expr -> map : '$1'. access_expr -> tuple : '$1'. access_expr -> 'true' : ?id('$1'). access_expr -> 'false' : ?id('$1'). access_expr -> 'nil' : ?id('$1'). access_expr -> bin_string : build_bin_string('$1'). access_expr -> list_string : build_list_string('$1'). access_expr -> bit_string : '$1'. access_expr -> sigil : build_sigil('$1'). access_expr -> max_expr : '$1'. %% Aliases and properly formed calls. Used by map_expr. max_expr -> atom : ?exprs('$1'). max_expr -> atom_safe : build_quoted_atom('$1', true). max_expr -> atom_unsafe : build_quoted_atom('$1', false). max_expr -> parens_call call_args_parens : build_identifier('$1', '$2'). max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3'). max_expr -> dot_alias : '$1'. bracket_arg -> open_bracket kw close_bracket : build_list('$1', '$2'). bracket_arg -> open_bracket container_expr close_bracket : build_list('$1', '$2'). bracket_arg -> open_bracket container_expr ',' close_bracket : build_list('$1', '$2'). bracket_expr -> dot_bracket_identifier bracket_arg : build_access(build_identifier('$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_identifier('$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,nil}]]. do_block -> do_eoe stab end_eoe : [[{do, build_stab(reverse('$2'))}]]. do_block -> do_eoe block_list 'end' : [[{do, nil}|'$2']]. do_block -> do_eoe stab_eoe block_list 'end' : [[{do, build_stab(reverse('$2'))}|'$3']]. eoe -> eol : '$1'. eoe -> ';' : '$1'. eoe -> eol ';' : '$1'. fn_eoe -> 'fn' : '$1'. fn_eoe -> 'fn' eoe : '$1'. 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'|'$1']. stab_eoe -> stab : '$1'. stab_eoe -> stab eoe : '$1'. stab_expr -> expr : '$1'. stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2'). stab_expr -> empty_paren stab_op_eol stab_maybe_expr : build_op('$2', [], '$3'). stab_expr -> call_args_no_parens_all stab_op_eol stab_maybe_expr : build_op('$2', unwrap_when(unwrap_splice('$1')), '$3'). stab_expr -> stab_parens_many stab_op_eol stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3'). stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr : build_op('$4', [{'when', meta('$2'), unwrap_splice('$1') ++ ['$3']}], '$5'). stab_maybe_expr -> 'expr' : '$1'. stab_maybe_expr -> '$empty' : nil. block_item -> block_eoe stab_eoe : {?exprs('$1'), build_stab(reverse('$2'))}. block_item -> block_eoe : {?exprs('$1'), nil}. block_list -> block_item : ['$1']. block_list -> block_item block_list : ['$1'|'$2']. %% Helpers open_paren -> '(' : '$1'. open_paren -> '(' eol : '$1'. close_paren -> ')' : '$1'. close_paren -> eol ')' : '$2'. empty_paren -> open_paren ')' : '$1'. open_bracket -> '[' : '$1'. open_bracket -> '[' eol : '$1'. close_bracket -> ']' : '$1'. close_bracket -> eol ']' : '$2'. open_bit -> '<<' : '$1'. open_bit -> '<<' eol : '$1'. close_bit -> '>>' : '$1'. close_bit -> eol '>>' : '$2'. open_curly -> '{' : '$1'. open_curly -> '{' eol : '$1'. close_curly -> '}' : '$1'. close_curly -> eol '}' : '$2'. % Operators add_op_eol -> add_op : '$1'. add_op_eol -> add_op eol : '$1'. add_op_eol -> dual_op : '$1'. add_op_eol -> dual_op eol : '$1'. mult_op_eol -> mult_op : '$1'. mult_op_eol -> mult_op eol : '$1'. hat_op_eol -> hat_op : '$1'. hat_op_eol -> hat_op eol : '$1'. two_op_eol -> two_op : '$1'. two_op_eol -> two_op eol : '$1'. pipe_op_eol -> pipe_op : '$1'. pipe_op_eol -> pipe_op eol : '$1'. capture_op_eol -> capture_op : '$1'. capture_op_eol -> capture_op eol : '$1'. 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'. match_op_eol -> match_op : '$1'. match_op_eol -> match_op eol : '$1'. and_op_eol -> and_op : '$1'. and_op_eol -> and_op eol : '$1'. or_op_eol -> or_op : '$1'. or_op_eol -> or_op eol : '$1'. in_op_eol -> in_op : '$1'. 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'. stab_op_eol -> stab_op : '$1'. stab_op_eol -> stab_op eol : '$1'. at_op_eol -> at_op : '$1'. at_op_eol -> at_op eol : '$1'. comp_op_eol -> comp_op : '$1'. comp_op_eol -> comp_op eol : '$1'. rel_op_eol -> rel_op : '$1'. rel_op_eol -> rel_op eol : '$1'. arrow_op_eol -> arrow_op : '$1'. arrow_op_eol -> arrow_op eol : '$1'. % 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 -> aliases : {'__aliases__', meta('$1', 0), ?exprs('$1')}. dot_alias -> matched_expr dot_op aliases : build_dot_alias('$2', '$1', '$3'). 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'). parens_call -> dot_paren_identifier : '$1'. parens_call -> matched_expr dot_call_op : {'.', meta('$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_one_ambig_expr : '$1'. call_args_no_parens_expr -> no_parens_many_expr : throw_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 -> no_parens_one_ambig_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 : throw_no_parens_strict('$1'). call_args_no_parens_many_strict -> open_paren call_args_no_parens_many close_paren : throw_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 : throw_no_parens_many_strict('$1'). container_args_base -> container_expr : ['$1']. container_args_base -> container_args_base ',' container_expr : ['$3'|'$1']. container_args -> container_args_base : lists:reverse('$1'). container_args -> container_args_base ',' : lists:reverse('$1'). container_args -> container_args_base ',' kw : lists: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 : throw_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 -> empty_paren : []. call_args_parens -> open_paren no_parens_expr close_paren : ['$2']. call_args_parens -> open_paren kw close_paren : ['$2']. call_args_parens -> open_paren call_args_parens_base close_paren : reverse('$2'). call_args_parens -> open_paren call_args_parens_base ',' kw close_paren : reverse(['$4'|'$2']). % KV kw_eol -> kw_identifier : ?exprs('$1'). kw_eol -> kw_identifier eol : ?exprs('$1'). kw_eol -> kw_identifier_safe : build_quoted_atom('$1', true). kw_eol -> kw_identifier_safe eol : build_quoted_atom('$1', true). kw_eol -> kw_identifier_unsafe : build_quoted_atom('$1', false). kw_eol -> kw_identifier_unsafe eol : build_quoted_atom('$1', false). kw_base -> kw_eol container_expr : [{'$1', '$2'}]. kw_base -> kw_base ',' kw_eol container_expr : [{'$3', '$4'}|'$1']. kw -> kw_base : reverse('$1'). kw -> kw_base ',' : reverse('$1'). call_args_no_parens_kw_expr -> kw_eol call_args_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']. % Lists list_args -> kw : '$1'. list_args -> container_args_base : reverse('$1'). list_args -> container_args_base ',' : reverse('$1'). list_args -> container_args_base ',' kw : reverse('$1', '$3'). list -> open_bracket ']' : build_list('$1', []). list -> open_bracket list_args close_bracket : build_list('$1', '$2'). % Tuple tuple -> open_curly '}' : build_tuple('$1', []). tuple -> open_curly container_args close_curly : build_tuple('$1', '$2'). % Bitstrings bit_string -> open_bit '>>' : build_bit('$1', []). bit_string -> open_bit container_args close_bit : build_bit('$1', '$2'). % Map and structs %% Allow unquote/@something/aliases inside maps and structs. map_expr -> max_expr : '$1'. map_expr -> dot_identifier : build_identifier('$1', nil). map_expr -> at_op_eol map_expr : build_unary_op('$1', '$2'). 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 -> map_expr : '$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 : {'$2', '$1', '$3'}. assoc_update_kw -> unmatched_expr pipe_op_eol kw : {'$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 close_curly : '$1'. map_close -> assoc close_curly : '$1'. map_close -> assoc_base ',' kw close_curly : reverse('$1', '$3'). map_args -> open_curly '}' : build_map('$1', []). map_args -> open_curly map_close : build_map('$1', '$2'). map_args -> open_curly assoc_update close_curly : build_map_update('$1', '$2', []). map_args -> open_curly assoc_update ',' close_curly : build_map_update('$1', '$2', []). map_args -> open_curly assoc_update ',' map_close : build_map_update('$1', '$2', '$4'). map_args -> open_curly assoc_update_kw close_curly : build_map_update('$1', '$2', []). struct_op -> '%' : '$1'. struct_op -> '%' eol : '$1'. map -> map_op map_args : '$2'. map -> struct_op map_expr map_args : {'%', meta('$1'), ['$2', '$3']}. map -> struct_op map_expr eol map_args : {'%', meta('$1'), ['$2', '$4']}. Erlang code. -define(id(Node), element(1, Node)). -define(line(Node), element(2, Node)). -define(exprs(Node), element(3, 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}]}]). -import(lists, [reverse/1, reverse/2]). meta(Line, Counter) -> [{counter,Counter}|meta(Line)]. meta({Line, Column, EndColumn}) when is_integer(Line), is_integer(Column), is_integer(EndColumn) -> [{line, Line}]; meta(Node) -> meta(?line(Node)). %% Operators build_op({_Kind, Line, 'in'}, {UOp, _, [Left]}, Right) when ?rearrange_uop(UOp) -> {UOp, meta(Line), [{'in', meta(Line), [Left, Right]}]}; build_op({_Kind, Line, Op}, Left, Right) -> {Op, meta(Line), [Left, Right]}. build_unary_op({_Kind, Line, Op}, Expr) -> {Op, meta(Line), [Expr]}. build_list(Marker, Args) -> {Args, ?line(Marker)}. build_tuple(_Marker, [Left, Right]) -> {Left, Right}; build_tuple(Marker, Args) -> {'{}', meta(Marker), Args}. build_bit(Marker, Args) -> {'<<>>', meta(Marker), Args}. build_map(Marker, Args) -> {'%{}', meta(Marker), Args}. build_map_update(Marker, {Pipe, Left, Right}, Extra) -> {'%{}', meta(Marker), [build_op(Pipe, Left, Right ++ Extra)]}. %% Blocks build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> {'__block__', [], Exprs}; build_block([{unquote_splicing,_,Args}]=Exprs) when length(Args) =< 2 -> {'__block__', [], Exprs}; build_block([Expr]) -> Expr; build_block(Exprs) -> {'__block__', [], Exprs}. %% Dots build_dot_alias(Dot, {'__aliases__', _, Left}, {'aliases', _, Right}) -> {'__aliases__', meta(Dot), Left ++ Right}; build_dot_alias(_Dot, Atom, {'aliases', {Line, _, _}, _}) when is_atom(Atom) -> throw_bad_atom(Line); build_dot_alias(Dot, Other, {'aliases', _, Right}) -> {'__aliases__', meta(Dot), [Other|Right]}. build_dot(Dot, Left, Right) -> {'.', meta(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, Args2) -> Identifier = build_identifier(Dot, Args1), Meta = element(2, Identifier), {Identifier, Meta, Args2}. build_identifier({'.', Meta, _} = Dot, Args) -> FArgs = case Args of nil -> []; _ -> Args end, {Dot, Meta, FArgs}; build_identifier({Keyword, Line}, Args) when Keyword == fn -> {fn, meta(Line), Args}; build_identifier({op_identifier, Line, Identifier}, [Arg]) -> {Identifier, [{ambiguous_op,nil}|meta(Line)], [Arg]}; build_identifier({_, Line, Identifier}, Args) -> {Identifier, meta(Line), Args}. %% Fn build_fn(Op, Stab) -> {fn, meta(Op), Stab}. %% Access build_access(Expr, {List, Line}) -> Meta = meta(Line), {{'.', Meta, ['Elixir.Access', get]}, Meta, [Expr, List]}. %% Interpolation aware build_sigil({sigil, Line, Sigil, Parts, Modifiers}) -> Meta = meta(Line), {list_to_atom("sigil_" ++ [Sigil]), Meta, [ {'<<>>', Meta, string_parts(Parts)}, Modifiers ]}. build_bin_string({bin_string, _Line, [H]}) when is_binary(H) -> H; build_bin_string({bin_string, Line, Args}) -> {'<<>>', meta(Line), string_parts(Args)}. build_list_string({list_string, _Line, [H]}) when is_binary(H) -> elixir_utils:characters_to_list(H); build_list_string({list_string, Line, Args}) -> Meta = meta(Line), {{'.', Meta, ['Elixir.String', to_char_list]}, Meta, [{'<<>>', Meta, string_parts(Args)}]}. build_quoted_atom({_, _Line, [H]}, Safe) when is_binary(H) -> Op = binary_to_atom_op(Safe), erlang:Op(H, utf8); build_quoted_atom({_, Line, Args}, Safe) -> Meta = meta(Line), {{'.', Meta, [erlang, binary_to_atom_op(Safe)]}, Meta, [{'<<>>', Meta, string_parts(Args)}, utf8]}. binary_to_atom_op(true) -> binary_to_existing_atom; binary_to_atom_op(false) -> binary_to_atom. string_parts(Parts) -> [string_part(Part) || Part <- Parts]. string_part(Binary) when is_binary(Binary) -> Binary; string_part({Line, Tokens}) -> Form = string_tokens_parse(Tokens), Meta = meta(Line), {'::', Meta, [{{'.', Meta, ['Elixir.Kernel', to_string]}, Meta, [Form]}, {binary, Meta, nil}]}. string_tokens_parse(Tokens) -> case parse(Tokens) of {ok, Forms} -> Forms; {error, _} = Error -> throw(Error) end. %% Keywords build_stab([{'->', Meta, [Left, Right]}|T]) -> build_stab(Meta, T, Left, [Right], []); build_stab(Else) -> build_block(Else). build_stab(Old, [{'->', New, [Left, Right]}|T], Marker, Temp, Acc) -> H = {'->', Old, [Marker, build_block(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 = {'->', Meta, [Marker, build_block(reverse(Temp))]}, reverse([H|Acc]). %% 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. to_block([One]) -> One; to_block(Other) -> {'__block__', [], reverse(Other)}. %% Errors throw(Line, Error, Token) -> throw({error, {Line, ?MODULE, [Error, Token]}}). throw_no_parens_strict(Token) -> throw(?line(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: ", "'('"). throw_no_parens_many_strict(Token) -> Line = case lists:keyfind(line, 1, element(2, Token)) of {line, L} -> L; false -> 0 end, throw(Line, "unexpected comma. Parentheses are required to solve ambiguity " "in nested calls. Syntax error before: ", "','"). throw_bad_atom(Line) -> throw(Line, "atom cannot be followed by an alias. If the '.' was meant to be " "part of the atom's name, the name must be quoted. Syntax error before: ", "'.'").