I noticed this when splitting `not` from the other unary operators in IntelliJ Elixir's lexer grammar (because I can only do spacing rules on lexer tokens, so I need NOT_OPERATOR separate from UNARY_OPERATOR for
https://github.com/KronicDeth/intellij-elixir/issues/98 so I can have no space between the symbolic operators, but require the space after `not` as it will blend into the argument otherwise). I'm not sure this is really a problem or not. It's just a weird edge-case that I produced since I test unary numeric and unary non-numeric operators.
`not` is part of `unary_op_eol` (
https://github.com/elixir-lang/elixir/blob/39dd31c7d96f302986579bb6938d23c9db101661/lib/elixir/src/elixir_parser.yrl#L73), which also contains `+`, `-`, `!`, `^`, `~~~`.
Let's start with unary numeric at the top-level
iex> Code.string_to_quoted("not 1")
{:ok, {:not, [line: 1], [1]}}
iex> Code.string_to_quoted("! 1")
{:ok, {:!, [line: 1], [1]}}
Next, let's do something weird and make the `1` act something you can do dot calls on. This is gibberish semantically in current Elixir, but something the syntax allows.
iex> Code.string_to_quoted("not 1.(2)")
{:ok, {{:., [line: 1], [{:not, [line: 1], [1]}]}, [line: 1], [2]}}
iex> Code.string_to_quoted("! 1.(2)")
{:ok, {{:., [line: 1], [{:!, [line: 1], [1]}]}, [line: 1], [2]}}
So, the precedence of the AST is the same: (1) `not`/`!` on `1`; (2) dot call with `2` on the result of (1).
Finally, let's try to capture the weird syntax
iex> Code.string_to_quoted("¬ 1.(2)")
{:ok, {:&, [line: 1], [{:not, [line: 1], [{{:., [line: 1], [1]}, [line: 1], [2]}]}]}}
iex> Code.string_to_quoted("&! 1.(2)")
{:ok, {:&, [line: 1], [{{:., [line: 1], [{:!, [line: 1], [1]}]}, [line: 1], [2]}]}}
So the precedence of the symbolic `!` and word `not` is no longer the same.
For `not`: (1) `1.(2)` is called; (2) `not` the result of (1)`; (3) capture
For `!`: `!1` is called; (2) `.(2)` is called on the result of (1); (3) capture
What has happened is that `not` has lost it's "keywordness" and is moving the position of a normal function name (like `foo` below)
iex> Code.string_to_quoted("&foo 1.(2)")
{:ok, {:&, [line: 1], [{:foo, [line: 1], [{{:., [line: 1], [1]}, [line: 1], [2]}]}]}}
So, this example is gibberish. Does it apply to non-numeric unary operations?
iex> Code.string_to_quoted("&! a.(2)")
{:ok, {:&, [line: 1], [{:!, [line: 1], [{{:., [line: 1], [{:a, [line: 1], nil}]}, [line: 1], [2]}]}]}}
iex> Code.string_to_quoted("¬ a.(2)")
{:ok, {:&, [line: 1], [{:not, [line: 1], [{{:., [line: 1], [{:a, [line: 1], nil}]}, [line: 1], [2]}]}]}}
iex(56)> Code.string_to_quoted("&foo a.(2)")
{:ok, {:&, [line: 1], [{:foo, [line: 1], [{{:., [line: 1], [{:a, [line: 1], nil}]}, [line: 1], [2]}]}]}}
So, it's all consistent there: (1) `a.(2)` is evaluated; (2) the operator/function call; and (3) it's all captured.
So, is the inconsistency in unary numeric `not` a bug in the native grammar or something to be expected? If the unary operators should bind more tightly to numerics than non-numerics (as is my understanding of the current grammar) then `{:ok, {:&, [line: 1], [{{:., [line: 1], [{:!, [line: 1], [1]}]}, [line: 1], [2]}]}}` is correct and `not` should be `{:ok, {:&, [line: 1], [{{:., [line: 1], [{:not, [line: 1], [1]}]}, [line: 1], [2]}]}}`.
The unary numeric operations in current language semantics seem to be there for positional captures, like `&1`, so this is mostly me bringing it up because I have to change IntelliJ Elixir's grammar to take this behaviour for unary numeric `not` into account separately from symbolic unary numerics.