Why Macro.prewalk/3 is adding parens when applying fun to given ast node?

Hi,
I am wondering thy elixir added parens at the end not just simply changed \* to + and + to \*

Interactive Elixir (1.19.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 5 * 3 + 7
22
iex(2)> ast = quote do: 5 + 3 * 7
{:+, [context: Elixir, imports: [{1, Kernel}, {2, Kernel}]],
 [5, {:*, [context: Elixir, imports: [{2, Kernel}]], [3, 7]}]}
iex(3)> new_ast = Macro.prewalk(ast, fn
          {:+, meta, children} -> {:*, meta, children}
          {:*, meta, children} -> {:+, meta, children}
          other -> other
        end)
{:*, [context: Elixir, imports: [{1, Kernel}, {2, Kernel}]],
 [5, {:+, [context: Elixir, imports: [{2, Kernel}]], [3, 7]}]}
iex(4)> Code.eval_quoted(ast)
{26, []}
iex(5)> Code.eval_quoted(new_ast)
{50, []}
iex(6)> Macro.to_string(ast)
"5 + 3 * 7"
iex(7)> Macro.to_string(new_ast)
"5 * (3 + 7)"
iex(8)> 

1 Like

I have not confirmed this, but I expect that without the parens the ast would not match the order of execution the code represents. To change this without adding parens would require not only changing the function (/operator), but also the order of operations.

2 Likes

If you’d look closely at the original ast, you’ll see it’s actually (in LISP notation) (+ 5 (* 3 7)). As we parse, left to right, we get (+ 5 …) and then we parse what’s next: (* 3 7).

Actually, what you have as ast would be 5 + (3 * 7) but Macto.to_string/1 shows it without parentheses because it’s smart enough. Then you blindly replace signs and now the precedence matters and parentheses stay.

2 Likes

Thanks, it is understandable after your explanation