Nested blocks with direct AST manipulation

Let’s say I were to define the following macro for my own nefarious purposes:

defmodule Block do

  defmacro my_macro do
    {:__block__, [], [
      quote(do: x = 1),
      {:__block__, [], [
        quote(do: x = 2),
        quote(do: x)
      ]}
    ]}
  end
end

The Elixir docs say clearly that this should never ever be done, but let’s assume I did it anyway. It seems to work fine:

iex(23)> require Block
Block
iex(24)> Block.my_macro
2

I thought I might want to compile the following into the nested block above:

(let (x 1)
  (let (x 2)
    x))

I can do that without the nested blocks, but this form seems easier to generate recursively from lisp’s AST. I prefer to let the Elixir compiler do the heavy duty AST manipulation, and in any case I guess those two nested blocks will end up collapsed any way (and yes, if I weren’t so lazy I’d collapse them myself). Is there any serious problem with this approach?

1 Like

I would just leave the block’s in, I do that anyway and have not noticed anything odd in the final code generation that could affect speed. :slight_smile:

/me uses lots and lots of __block__'s in their macro’s, mostly to add line and typing information to constants like integers and such that have malformed non 3-tuple AST nodes

Your example is just this code anyway. :slight_smile:

(
  x = 1

  (
    x = 2
    x
  )
)
2 Likes

You’re right… Didn’t think of it. Unless someone else knows about any real problem with nested blocks I’ll mark your answer as a solution.

EDIT: which can be written even more concisely like this:

(x=1; (x=2; x))
2 Likes

I literally took your AST and parsed and formatted it:

iex(2)> q = {:__block__, [], [
...(2)>       quote(do: x = 1),
...(2)>       {:__block__, [], [
...(2)>         quote(do: x = 2),
...(2)>         quote(do: x)
...(2)>       ]}
...(2)>     ]}
{
  :__block__,
  [],
  [
    {:=, [], [{:x, [], Elixir}, 1]},
    {:__block__, [], [{:=, [], [{:x, [], Elixir}, 2]}, {:x, [], Elixir}]}
  ]
}
iex(3)> q |> Macro.prewalk(&Macro.expand(&1, __ENV__)) |> Macro.to_string() |> Code.format_string!() |> IO.puts()
x = 1

(
  x = 2
  x
)
:ok

^.^

The formatter breaks it up. ^.^

1 Like

wow! thats cool. Thanks for sharing.I found a lot of interesting information here. A really good post, very thankful and hopeful that you will write many more posts like this one.
with regards
miles smith