(Sorry, for the long post, but lots of quoted AST)
As part of intellij-elixir, I have fairly thorough tests now to ensure my Grammar Kit
grammar (an LL grammar with parsing expression grammar style positive and negative look-ahead) parses the same quoted form as Elixir's yecc
grammar (an LALR grammar like you'd write for yacc) parsing the same way since one is left-most derivation and the original is right-most derivation. These tests have shown an inconsistency in precedence. The precedence according to elixir_parser.yrl is as follows:
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.
So, my reading of this is that unary_op_eol should be encapsulated by any operator with a lower number (so early in the above list). This appears to hold true when quoting test expressions with Code.string_to_quoted/1:
iex(intellij_elixir@tartarus)27> Code.string_to_quoted("+one when +two")
{:ok,
{:when, [line: 1],
[{:+, [line: 1], [{:one, [line: 1], nil}]},
{:+, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)28> Code.string_to_quoted("+one :: +two")
{:ok,
{:::, [line: 1],
[{:+, [line: 1], [{:one, [line: 1], nil}]},
{:+, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)29> Code.string_to_quoted("!one :: !two")
{:ok,
{:::, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)30> Code.string_to_quoted("!one | !two")
{:ok,
{:|, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)31> Code.string_to_quoted("!one = !two")
{:ok,
{:=, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)32> Code.string_to_quoted("!one || !two")
{:ok,
{:||, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)33> Code.string_to_quoted("!one && !two")
{:ok,
{:&&, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)34> Code.string_to_quoted("!one == !two")
{:ok,
{:==, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)35> Code.string_to_quoted("!one < !two")
{:ok,
{:<, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)36> Code.string_to_quoted("!one |> !two")
{:ok,
{:|>, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
For everything from when through arrow (like `|>`), this can be summarized as the infix binary operator being the outer call, but for `in`, it switches:
iex(intellij_elixir@tartarus)37> Code.string_to_quoted("!one in !two")
{:ok,
{:!, [line: 1],
[{:in, [line: 1],
[{:one, [line: 1], nil}, {:!, [line: 1], [{:two, [line: 1], nil}]}]}]}}
Ok, so I could assume that for some reason that at precedence 180, something weird happens and everything past `in` should be inside `!`, which is unary and so higher precedence (number 300), but everything after `in` also has the infix operator on the outside:
iex(intellij_elixir@tartarus)38> Code.string_to_quoted("!one ++ !two")
{:ok,
{:++, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)39> Code.string_to_quoted("!one + !two")
{:ok,
{:+, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)40> Code.string_to_quoted("!one * !two")
{:ok,
{:*, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
iex(intellij_elixir@tartarus)41> Code.string_to_quoted("!one ^^^ !two")
{:ok,
{:^^^, [line: 1],
[{:!, [line: 1], [{:one, [line: 1], nil}]},
{:!, [line: 1], [{:two, [line: 1], nil}]}]}}
So, what's happening in the grammar to make `in` special? If I'm misunderstanding how to read the operator precedence it could lead to systematic errors in intellij-elixir's translation from LALR to LL. (And I had plenty of problems converting LALR to LL, so I'm fine with it being a problem with me reading the grammar, I just would like help understanding how to read it properly.)