4258 words · 17 min read

Mirror Mirror On The Wall

tech hacking elxiir phoenix liveview silly Feb 21, 2025

Mirror Mirror on the wall

Lol, Hi, so

I was thinking about how people used to mirror websites the other night and I thought of a funny joke.

What if one was born more recently, didn’t remember this, and was told to mirror a site?

Well, that would, possibly, for me at least, make me think maybe it should display its own source code.

JavaScript is great, and it lets you view its source and play with it in the console. It seems reasonable to think that I should be able to see my Elixir source code from the client side, too. And let’s be real, I love a good meta bit.

So, I set out to do this.

(An Aside) Making a quick project to follow along

If you want to follow along, we can make a quick project and ensure that you can do everything in this post, if you are such a silly person that this sounds fun to you.

We begin with a basic live view app

mix phx.new mirror --live --no-ecto

This is all we need, since it will just be one page. From here, to follow along at home, we can use any code samples that have a comment at the top like so:

# lib/foo.ex

defmodule Foo do
  # we will use this module later to decompile
  @compile [:debug_info]
  def bar, do: 1
end

For convenience, in the parts below we removed the bar function for explaining all this a little quicker.

Starting off with a basic funny bit

We all begin with a joke. In the beginning, I wanted to just render the source code however I could, and figured cheating was okay. So, I figured if I made the file and knew it would be on github I could always get it.

This looked super simple, the entire LiveView is just this:

defmodule BlogWeb.MirrorLive do
  use BlogWeb, :live_view
  require Logger

  @source_url "https://raw.githubusercontent.com/notactuallytreyanastasio/blog/main/lib/blog_web/live/mirror_live.ex"

  def mount(_params, _session, socket) do
    if connected?(socket) do
      # Fetch source code asynchronously when client connects
      Task.async(fn -> fetch_source_code() end)
    end

    {:ok,
     assign(socket,
       source_code: "Loading source code...",
       page_title: "I am looking at myself",
       meta_attrs: [
         %{name: "title", content: "Mirror Mirror on the wall"},
         %{name: "description", content: "A page that shows its own source code"},
         %{property: "og:title", content: "Mirror Mirror on the wall"},
         %{property: "og:description", content: "A page that shows its own source code"},
         %{property: "og:type", content: "website"}
       ]
     )}
  end

  def handle_info({ref, source_code}, socket) when is_reference(ref) do
    # Flush the DOWN message
    Process.demonitor(ref, [:flush])

    # Split into lines first, then characters
    characters = source_code
      |> String.split("\n")
      |> Enum.map(fn line ->
        line
        |> String.graphemes()
        |> Enum.map(fn char ->
          %{
            char: char,
            duration: :rand.uniform(10) + 5,
            delay: :rand.uniform(5000),
            direction: if(:rand.uniform() > 0.5, do: 1, else: -1)
          }
        end)
      end)
    {:noreply, assign(socket, lines: characters)}
  end

  defp fetch_source_code do
    Req.get!("https://raw.githubusercontent.com/notactuallytreyanastasio/blog/main/lib/blog_web/live/mirror_live.ex") |> Map.get(:body)
  end

  def render(assigns) do
    ~H"""
    <div class="min-h-screen bg-gray-900 text-white p-8">
      <div class="max-w-4xl mx-auto">
        <h1 class="text-3xl font-bold mb-8">Mirror Mirror on the wall, who's the most meta of them all?</h1>
        <div class="bg-gray-800 rounded-lg p-6 shadow-lg">
          <pre class="text-sm font-mono" style="tab-size: 2;"><code class="language-elixir"><%= if assigns[:lines] do %><%= for line <- @lines do %><%= for char <- line do %><span style={"display: inline-block; animation: spin#{char.duration} #{char.duration}s linear #{char.delay}ms infinite;"}><%= char.char %></span><% end %>
<% end %><% else %>Loading source code...<% end %></code></pre>
        </div>
      </div>
    </div>

    <style>
      <%= for duration <- 5..15 do %>
        @keyframes spin<%= duration %> {
          from { transform: rotate(0deg); }
          to { transform: rotate(<%= if rem(duration, 2) == 0, do: "360", else: "-360" %>deg); }
        }
      <% end %>
    </style>
    """
  end
end

The only fun part here is the frames animating every character individually.

I figured why not just make it trippy, so I got some help from Claude.

This was a decent start, but I figured I should actually decompile some bytecode. This was too easy to really consider our mission accomplished.

It looks pretty standard at this point, though it really kills the browser with all these animations.

I would have thought the browser performed a little better here, but I guess I can see how this would stress it out, looping through every character.

You can see a look at it here.

Let’s actually decompile the code

I figured to really do this, it would be necessary to decompile the code completely. This is going to prove a bit more of a challenge, but will be a very fun exercise. We will begin by attacking what methods we could possibly use here in general. The core of this is all going to center around Elixir’s macro system, and functionally abuse it.

We will derive this piece by piece and explain ourselves as we go along the way, but we will start off with a finished piece of code we can reference to understand the high level API we end up with here.

A basic decompiler

This is just for reference for now, but this is a peek at the API we end up with here:

defmodule CodeDecompiler do
  def decompile_to_string(module) when is_atom(module) do
    path = :code.which(module)
    
    case :beam_lib.chunks(path, [:abstract_code]) do
      {:ok, {_, [{:abstract_code, {:raw_abstract_v1, abstract_code}}]}} ->
        # Convert the abstract format to quoted expressions
        quoted = Enum.map(abstract_code, &abstract_code_to_quoted/1)
                |> Enum.reject(&is_nil/1)
                |> wrap_in_module(module)
        
        # Format the quoted expression into a string
        Macro.to_string(quoted)
        
      {:ok, {_, [{:abstract_code, none}]}} when none in [nil, :none] ->
        {:error, :no_abstract_code}
        
      {:error, :beam_lib, {:missing_chunk, _, _}} ->
        {:error, :no_debug_info}
        
      {:error, :beam_lib, error} ->
        {:error, {:beam_lib, error}}
        
      unexpected ->
        {:error, {:unexpected_chunk_format, unexpected}}
    end
  end

This is our highest level entrypoint and only public function.

We can break down what we are doing piece by piece here with some real code, and begin to understand this approach.

Decompiling something extremely basic

Let’s start off by making a file we can use as a piece to decompile:

# lib/foo.ex
defmodule Foo do
end

We will write this in lib/foo.ex and then restart our shell with iex -S mix so that we can ensure its compiled. With this, we can start to take a look at what we will get from our beginning statements above and begin to gradually reconstruct something that will get us what we are looking for to derive this source code naturally.

iex> path = :code.which(Foo)
iex> :beam_lib.chunks(path, [:abstract_code])

And this yields us quite a large chunk, which we are going to examine piece by piece:

{Foo,
  [
    abstract_code: {:raw_abstract_v1,
     [
       {:attribute, 1, :file, {~c"lib/foo.ex", 1}},
       {:attribute, 1, :module, Foo},
       {:attribute, 1, :compile, [:no_auto_import]},
       {:attribute, 1, :export, [__info__: 1]},
       {:attribute, 1, :spec,
        {{:__info__, 1},
         [
           {:type, 1, :fun,
            [
              {:type, 1, :product,
               [
                 {:type, 1, :union,
                  [
                    {:atom, 1, :attributes},
                    {:atom, 1, :compile},
                    {:atom, 1, :functions},
                    {:atom, 1, :macros},
                    {:atom, 1, :md5},
                    {:atom, 1, :exports_md5},
                    {:atom, 1, :module},
                    {:atom, 1, :deprecated},
                    {:atom, 1, :struct}
                  ]}
               ]},
              {:type, 1, :any, []}
            ]}
         ]}},
       {:function, 0, :__info__, 1,
        [
          {:clause, 0, [{:atom, 0, :module}], [], [{:atom, 0, Foo}]},
          {:clause, 0, [{:atom, 0, :functions}], [], [nil: 0]},
          {:clause, 0, [{:atom, 0, :macros}], [], [nil: 0]},
          {:clause, 0, [{:atom, 0, :struct}], [], [{:atom, 0, nil}]},
          {:clause, 0, [{:atom, 0, :exports_md5}], [],
           [
             {:bin, 0,
              [
                {:bin_element, 0,
                 {:string, 0,
                  [38, 59, 76, 152, 217, 42, 179, 68, 34, 190, 13, ...]},
                 :default, :default}
              ]}
           ]},
          {:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :attributes}}],
           [],
           [
             {:call, 0,
              {:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
              [{:atom, 0, Foo}, {:var, 0, :Key}]}
           ]},
          {:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :compile}}], [],
           [
             {:call, 0,
              {:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
              [{:atom, 0, Foo}, {:var, 0, :Key}]}
           ]},
          {:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :md5}}], [],
           [
             {:call, 0,
              {:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
              [{:atom, 0, Foo}, {:var, 0, :Key}]}
           ]},
          {:clause, 0, [{:atom, 0, :deprecated}], [], [nil: 0]}
        ]}
     ]}
  ]}

Let’s attack this piece by piece.

We can begin by thinking of a bit of a simpler match to break down the multiple structures we see being returned.

path = :code.which(Foo)
{:ok, {Foo, [abstract_code: {:raw_abstract_v1, ast}]}} = :beam_lib.chunks(path, [:abstract_code])

What we are really concerned with here, overall, is the AST.

If we have an AST, we can reverse that into source code, right?

So, thinking of it this way, we can get a good look at this specific representation:

:beam_lib.chunks(...) produces
                      |
                      v
{:ok, {ModuleName, [abstract_code: {:raw_abstract_v1, <list_of_forms>}]}}
                                   |
                                   v
                      <list_of_forms> = [
  {:attribute, ...},
  {:attribute, ...},
  {:function, ...},
  ...
]

Now, if we want to think of the top level of this, we are defining module attributes.

These module attributes are automatically included in our module definition when it is compiled down to Erlang, and later bytecode.

In this case, we see:

{:attribute, 1, :file, {~c"lib/foo.ex", 1}}

This is giving us a representation like so:

{:attribute, <LineNumber>, :file, {<file_name>, <line_number>}}

which is to say:

:attribute
├─ 1                      (the line number in the source code)
├─ :file                  (the type of attribute)
└─ {~c"lib/foo.ex", 1}    (the file name and the line number)

Now, with this we can start cooking. This is something we could reconstruct source from.

The next piece is a little simpler:

{:attribute, 1, :module, Foo}

With this, we are basically just declaring that the module has this name at compile time.

:attribute
├─ 1         (line number)
├─ :module   (the type of attribute)
└─ Foo       (the module name)

Next, we get something like this:

{:attribute, 1, :compile, [:no_auto_import]}

Which means

:attribute
├─ 1                     (line number)
├─ :compile              (the type of attribute)
└─ [:no_auto_import]     (compile option)

And in this case is telling us: “Don’t auto-import general Elixir macros/functions (like some built-in Kernel macros).”.

In general, Elixir modules have :no_auto_import set by default to help avoid certain name collisions.

Now, we get to something more interesting: exporting functions.

{:attribute, 1, :export, [__info__: 1]}

This is another module attribute, which will be telling Erlang to publicly export the __info__/1 function.

This takes another shape:

{:attribute, <LineNumber>, :export, [{:function_atom, arity}, ...]}

The :export attribute is how Erlang denotes which functions are publicly exported to support our public API.

Reflection about the module (e.g., Foo.__info__(:functions), etc. is the key here.

Next, we get to a generated typespec that is coming along, even though we didn’t define it.

{:attribute, 1, :spec, {{:__info__, 1}, [ {:type, 1, :fun, ... } ]}}

This is the type specification (the @spec) for the exported function __info__/1.

We see this sort of representation below:

{:type, 1, :fun,
  [
    {:type, 1, :product, [...]},  # argument type
    {:type, 1, :any, []}          # return type
  ]
}

This means “The function __info__/1 is a function that takes one parameter of a certain union type (like :attributes | :compile | :functions | ...) and returns any“.

Almost through here, next we get the actual function definition for __info__/1:

{:function, 0, :__info__, 1, [clauses...]}

Here we get to the actual function definition for __info__/1. This is how Erlang’s abstract format represents a function with multiple clause definitions—each clause typically corresponds to a different pattern match.

{:function, 0, :__info__, 1,
 [
   {:clause, 0, [{:atom, 0, :module}], [], [{:atom, 0, Foo}]},
   {:clause, 0, [{:atom, 0, :functions}], [], [nil: 0]},
   {:clause, 0, [{:atom, 0, :macros}], [], [nil: 0]},
   {:clause, 0, [{:atom, 0, :struct}], [], [{:atom, 0, nil}]},
   ...
   {:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :attributes}}], [],
     [
       {:call, 0, {:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
        [{:atom, 0, Foo}, {:var, 0, :Key}]}
     ]
   },
   ...
 ]}

Each clause has:

{:clause, line, <pattern_list>, <guard_list>, <body_ast>}

Some clauses match {:atom, 0, :module}, some match {:atom, 0, :functions}, etc.

Some have a “catch-all” style with a match binding to :Key and then do :erlang.get_module_info(Foo, Key).

:clause
├─ 0            (line number)
├─ [{:atom, 0, :module}]    (pattern list)
├─ []                       (guard list)
└─ [{:atom, 0, Foo}]        (body AST)

Meaning: “If someone calls __info__(:module), return Foo.”

It is similar for :functions, :macros, etc.

Elixir auto-generates these for reflection.

Some clauses return nil explicitly (written as [nil: 0] in the AST, which is an Erlang way to represent an atom nil with line number 0).

Others call :erlang.get_module_info/2 to get further info about the module.

This is a minimal example of how even the simplest Elixir module turns into multiple lines of Abstract Format. It gives you a glimpse of how the BEAM environment sees your code by the time it’s ready to generate bytecode.

Now, we understand the pieces making this up and have an AST, but how do we get this turned into code?

Using the decompiled AST

The next piece of our code we will break down piece by piece to understand how we are going to start breaking this down.

We can see its full form here:

    ... snip ...

    case :beam_lib.chunks(path, [:abstract_code]) do
      {:ok, {_, [{:abstract_code, {:raw_abstract_v1, abstract_code}}]}} ->
        # Convert the abstract format to quoted expressions
        quoted = 
          abstract_code
          |> Enum.map(&abstract_code_to_quoted/1)
          |> Enum.reject(&is_nil/1)
          |> wrap_in_module(module)

    ... snip ...

We have a function here that is our primary entrypoint: abstract_code_to_quoted/1. This will be how we start getting the source code itself, by making quoted expressions that we can turn into “stringy” code.

This function looks something like this, and the first pieces are much simpler than the final bit

  # Skip module attribute as we handle it in wrap_in_module
  defp abstract_code_to_quoted({:attribute, _, :module, _}), do: nil
  # Skip exports
  defp abstract_code_to_quoted({:attribute, _, :export, _}), do: nil
  # Skip compile attributes
  defp abstract_code_to_quoted({:attribute, _, :compile, _}), do: nil 
  defp abstract_code_to_quoted({:attribute, line, name, value}) do
    quote line: normalize_line(line) do
      Module.put_attribute(__MODULE__, unquote(name), unquote(convert_attribute_value(value)))
    end
  end

Now, with abstract_code_to_quoted we are going to grab the line, normalize it, and then create some code to create the attributes.

To convert these attributes, we do something like this:

  defp convert_attribute_value(value) when is_atom(value) or is_integer(value) or is_float(value) or is_binary(value), do: value
  defp convert_attribute_value(value) when is_list(value), do: Enum.map(value, &convert_attribute_value/1)
  defp convert_attribute_value({a, b}), do: {convert_attribute_value(a), convert_attribute_value(b)}
  defp convert_attribute_value(other), do: other

This gets us the values we need to combine with what we are putting into our own module.

Next, we wrap the definitions into a module:

  # Wrap the collected definitions in a module
  defp wrap_in_module(definitions, module_name) do
    quote do
      defmodule unquote(module_name) do
        unquote_splicing(definitions)
      end
    end
  end

This allows us to have everything prepared to hand off to Macro.to_string, as we are basically constructing a macro from the AST.

If we look at things after creating quoted now we can see something like this:

{:defmodule, [context: CodeDecompiler, imports: [{2, Kernel}]],
 [
   Foo,
   [
     do: {:__block__, [],
      [
        {{:., [line: 1],
          [{:__aliases__, [line: 1, alias: false], [:Module]}, :put_attribute]},
         [line: 1],
         [{:__MODULE__, [line: 1], CodeDecompiler}, :file, {~c"lib/foo.ex", 1}]},
        {{:., [line: 1],
          [{:__aliases__, [line: 1, alias: false], [:Module]}, :put_attribute]},
         [line: 1],
         [
           {:__MODULE__, [line: 1], CodeDecompiler},
           :spec,
           {{:__info__, 1},
            [
              {:type, 1, :fun,
               [
                 {:type, 1, :product,
                  [
                    {:type, 1, :union,
                     [
                       {:atom, 1, :attributes},
                       {:atom, 1, :compile},
                       {:atom, 1, :functions},
                       {:atom, 1, :macros},
                       {:atom, 1, :md5},
                       {:atom, 1, :exports_md5},
                       {:atom, 1, :module},
                       {:atom, 1, :deprecated},
                       {:atom, 1, :struct}
                     ]}
                  ]},
                 {:type, 1, :any, []}
               ]}
            ]}
         ]},
        {:def,
         [line: 3, context: CodeDecompiler, imports: [{1, Kernel}, {2, Kernel}]],
         [
           {:bar, [line: 3, context: CodeDecompiler], []},
           [do: [[{:->, [line: 3], [[], {:__block__, [line: 3], [1]}]}]]]
         ]}
      ]}
   ]
 ]}

And, if we have consutrcted it right, we can finally just call Macro.to_string/1.

If we look at the docs, we see:

                            def to_string(tree, fun)

  @spec to_string(t(), (t(), String.t() -> String.t())) :: String.t()

deprecated: Use Macro.to_string/1 instead

Converts the given expression AST to a string.

The given fun is called for every node in the AST with two arguments: the AST
of the node being printed and the string representation of that same node. The
return value of this function is used as the final string representation for
that AST node.

This function discards all formatting of the original code.

## Examples

    Macro.to_string(quote(do: 1 + 2), fn
      1, _string -> "one"
      2, _string -> "two"
      _ast, string -> string
    end)
    #=> "one + two"

Well, this looks like we just might be able to get what we want, let’s try:

defmodule Foo do
  Module.put_attribute(__MODULE__, :file, {~c"lib/foo.ex", 1})

  Module.put_attribute(
    __MODULE__,
    :spec,
    {{:__info__, 1},
     [
       {:type, 1, :fun, [{:type, 1, :product, [{:type, 1, :union, [{:atom, 1, :attributes}, {:atom, 1, :compile}, {:atom, 1, :functions}, {:atom, 1, :macros}, {:atom, 1, :md5}, {:atom, 1, :exports_md5}, {:atom, 1, :module}, {:atom, 1, :deprecated}, {:atom, 1, :struct}]}]}, {:type, 1, :any, []}]}
     ]}
  )

  def bar() do
    [(-> 1)]
  end
end

Boom.

This isn’t the original source code!

But, its valid source code producing the same result.

We could call Foo.bar and return 1 and this is functionally good valid Elixir source.

Wrapping Up

What a weird little exercise, huh?

This is pretty useless, but I hope you had fun examining it.

Here is the final Decompiler and LiveView.

I extended this to support the entire LiveView, so its a lot more code than above.

# lib/mirror_web/mirror_live.ex
defmodule BlogWeb.MirrorLive do
  use BlogWeb, :live_view
  require Logger

  @source_url "https://raw.githubusercontent.com/notactuallytreyanastasio/blog/main/lib/blog_web/live/mirror_live.ex"

  def mount(_params, _session, socket) do
    if connected?(socket) do
      # Fetch source code asynchronously when client connects
      Task.async(fn -> fetch_source_code() end)
    end

    {:ok,
     assign(socket,
       source_code: "Loading source code...",
       page_title: "I am looking at myself",
       meta_attrs: [
         %{name: "title", content: "Mirror Mirror on the wall"},
         %{name: "description", content: "A page that shows its own source code"},
         %{property: "og:title", content: "Mirror Mirror on the wall"},
         %{property: "og:description", content: "A page that shows its own source code"},
         %{property: "og:type", content: "website"}
       ]
     )}
  end

  def handle_info({ref, source_code}, socket) when is_reference(ref) do
    # Flush the DOWN message
    Process.demonitor(ref, [:flush])

    # Split into lines first, then characters
    characters = source_code
      |> String.split("\n")
      |> Enum.map(fn line ->
        line
        |> String.graphemes()
        |> Enum.map(fn char ->
          %{
            char: char,
            duration: :rand.uniform(10) + 5,
            delay: :rand.uniform(5000),
            direction: if(:rand.uniform() > 0.5, do: 1, else: -1)
          }
        end)
      end)
    {:noreply, assign(socket, lines: characters)}
  end

  defp fetch_source_code do
    CodeDecompiler.decompile_to_string(__MODULE__)
  end

  def render(assigns) do
    ~H"""
    <div class="min-h-screen bg-gray-900 text-white p-8">
      <div class="max-w-4xl mx-auto">
        <h1 class="text-3xl font-bold mb-8">Mirror Mirror on the wall, who's the most meta of them all?</h1>
        <div class="bg-gray-800 rounded-lg p-6 shadow-lg">
          <pre class="text-sm font-mono" style="tab-size: 2;"><code class="language-elixir"><%= if assigns[:lines] do %><%= for line <- @lines do %><%= for char <- line do %><span style={"display: inline-block; animation: spin#{char.duration} #{char.duration}s linear #{char.delay}ms infinite;"}><%= char.char %></span><% end %>
<% end %><% else %>Loading source code...<% end %></code></pre>
        </div>
      </div>
    </div>

    <style>
      <%= for duration <- 5..15 do %>
        @keyframes spin<%= duration %> {
          from { transform: rotate(0deg); }
          to { transform: rotate(<%= if rem(duration, 2) == 0, do: "360", else: "-360" %>deg); }
        }
      <% end %>
    </style>
    """
  end
end

This really just is calling out to CodeDecompiler to get our source, if you ignore all the animation madness.


  defp fetch_source_code do
    CodeDecompiler.decompile_to_string(__MODULE__)
  end

And here is CodeDecompiler in its full glory extended to support the entire LiveView

# lib/code_decompiler.ex
defmodule CodeDecompiler do
  def decompile_to_string(module) when is_atom(module) do
    path = :code.which(module)
    
    case :beam_lib.chunks(path, [:abstract_code]) do
      {:ok, {_, [{:abstract_code, {:raw_abstract_v1, abstract_code}}]}} ->
        # Convert the abstract format to quoted expressions
        quoted = Enum.map(abstract_code, &abstract_code_to_quoted/1)
                |> Enum.reject(&is_nil/1)
                |> wrap_in_module(module)
        
        # Format the quoted expression into a string
        require IEx; IEx.pry
        Macro.to_string(quoted)
        
      {:ok, {_, [{:abstract_code, none}]}} when none in [nil, :none] ->
        {:error, :no_abstract_code}
        
      {:error, :beam_lib, {:missing_chunk, _, _}} ->
        {:error, :no_debug_info}
        
      {:error, :beam_lib, error} ->
        {:error, {:beam_lib, error}}
        
      unexpected ->
        {:error, {:unexpected_chunk_format, unexpected}}
    end
  end

  # Helper to normalize line numbers from either integers or {line, column} tuples
  defp normalize_line(line) when is_integer(line), do: line
  defp normalize_line({line, _column}) when is_integer(line), do: line
  defp normalize_line(_), do: 0

  # Wrap the collected definitions in a module
  defp wrap_in_module(definitions, module_name) do
    quote do
      defmodule unquote(module_name) do
        unquote_splicing(definitions)
      end
    end
  end

  # Module attributes
  defp abstract_code_to_quoted({:attribute, _, :module, _}), do: nil  # Skip module attribute as we handle it in wrap_in_module
  defp abstract_code_to_quoted({:attribute, _, :export, _}), do: nil  # Skip exports
  defp abstract_code_to_quoted({:attribute, _, :compile, _}), do: nil # Skip compile attributes
  defp abstract_code_to_quoted({:attribute, line, name, value}) do
    quote line: normalize_line(line) do
      Module.put_attribute(__MODULE__, unquote(name), unquote(convert_attribute_value(value)))
    end
  end

  # Functions
  defp abstract_code_to_quoted({:function, line, name, arity, clauses}) do
    # Skip module_info functions as they're automatically generated
    case name do
      :__info__ -> nil
      :module_info -> nil
      name when is_atom(name) ->
        function_clauses = Enum.map(clauses, &clause_to_quoted/1)
        
        quote line: normalize_line(line) do
          def unquote(name)(unquote_splicing(make_vars(arity))) do
            unquote(function_clauses)
          end
        end
    end
  end

  # Function clauses
  defp clause_to_quoted({:clause, line, params, guards, body}) do
    converted_params = Enum.map(params, &pattern_to_quoted/1)
    converted_guards = Enum.map(guards, &guard_to_quoted/1)
    converted_body = Enum.map(body, &expression_to_quoted/1)
    
    case converted_guards do
      [] ->
        quote line: normalize_line(line) do
          unquote_splicing(converted_params) -> unquote_splicing(converted_body)
        end
      guards ->
        quote line: normalize_line(line) do
          unquote_splicing(converted_params) when unquote_splicing(guards) -> unquote_splicing(converted_body)
        end
    end
  end

  # Patterns (used in function heads and pattern matching)
  defp pattern_to_quoted({:match, line, pattern1, pattern2}) do
    quote line: normalize_line(line) do
      unquote(pattern_to_quoted(pattern1)) = unquote(pattern_to_quoted(pattern2))
    end
  end

  # Add binary pattern support
  defp pattern_to_quoted({:bin, line, elements}) do
    quoted_elements = Enum.map(elements, &binary_element_to_quoted/1)
    quote line: normalize_line(line) do
      <<unquote_splicing(quoted_elements)>>
    end
  end

  defp pattern_to_quoted({:var, line, name}) do
    quote line: normalize_line(line) do
      unquote(Macro.var(name, nil))
    end
  end
  
  defp pattern_to_quoted({:integer, line, value}) do
    quote line: normalize_line(line) do
      unquote(value)
    end
  end
  
  defp pattern_to_quoted({:atom, line, value}) do
    quote line: normalize_line(line) do
      unquote(value)
    end
  end
  
  defp pattern_to_quoted({:cons, line, head, tail}) do
    quote line: normalize_line(line) do
      [unquote(pattern_to_quoted(head)) | unquote(pattern_to_quoted(tail))]
    end
  end
  
  defp pattern_to_quoted({:nil, line}) do
    quote line: normalize_line(line) do
      []
    end
  end
  
  defp pattern_to_quoted({:tuple, line, elements}) do
    quoted_elements = Enum.map(elements, &pattern_to_quoted/1)
    quote line: normalize_line(line) do
      {unquote_splicing(quoted_elements)}
    end
  end
  
  defp pattern_to_quoted({:map, line, pairs}) do
    quoted_pairs = Enum.map(pairs, fn 
      {:map_field_assoc, _, key, value} -> 
        {:%{}, [], [{pattern_to_quoted(key), pattern_to_quoted(value)}]}
      {:map_field_exact, _, key, value} ->
        {pattern_to_quoted(key), pattern_to_quoted(value)}
      {op, k, v} -> 
        {map_op_to_quoted(op), pattern_to_quoted(k), pattern_to_quoted(v)}
    end)
    quote line: normalize_line(line) do
      %{unquote_splicing(quoted_pairs)}
    end
  end

  # Guards
  defp guard_to_quoted(guards) when is_list(guards) do
    Enum.map(guards, fn guard -> guard_to_quoted_expr(guard) end)
  end

  defp guard_to_quoted(guard), do: guard_to_quoted_expr(guard)

  # Guard expressions
  defp guard_to_quoted_expr({:op, line, operator, left, right}) do
    quote line: normalize_line(line) do
      unquote({operator, [], [expression_to_quoted(left), expression_to_quoted(right)]})
    end
  end

  defp guard_to_quoted_expr({:op, line, operator, operand}) do
    quote line: normalize_line(line) do
      unquote({operator, [], [expression_to_quoted(operand)]})
    end
  end

  defp guard_to_quoted_expr({:call, line, {:remote, _, {:atom, _, module}, {:atom, _, fun}}, args}) do
    quoted_args = Enum.map(args, &expression_to_quoted/1)
    quote line: normalize_line(line) do
      unquote(module).unquote(fun)(unquote_splicing(quoted_args))
    end
  end
  
  defp guard_to_quoted_expr({:call, line, {:atom, _, fun}, args}) do
    quoted_args = Enum.map(args, &expression_to_quoted/1)
    quote line: normalize_line(line) do
      unquote(fun)(unquote_splicing(quoted_args))
    end
  end

  # Add support for variables and other basic terms in guards
  defp guard_to_quoted_expr(expr), do: expression_to_quoted(expr)

  # Expressions (function bodies)
  # Binary expressions need to come before general constructs
  defp expression_to_quoted({:bin, line, elements}) do
    quoted_elements = Enum.map(elements, &binary_element_to_quoted/1)
    quote line: normalize_line(line) do
      <<unquote_splicing(quoted_elements)>>
    end
  end

  # Anonymous functions
  defp expression_to_quoted({:fun, line, {:clauses, clauses}}) do
    quoted_clauses = Enum.map(clauses, fn {:clause, clause_line, params, guards, body} ->
      converted_params = Enum.map(params, &pattern_to_quoted/1)
      converted_guards = Enum.map(guards, &guard_to_quoted/1)
      converted_body = Enum.map(body, &expression_to_quoted/1)
      
      case converted_guards do
        [] ->
          {:->, [line: normalize_line(clause_line)],
           [converted_params, {:__block__, [], converted_body}]}
        guards ->
          {:->, [line: normalize_line(clause_line)],
           [[{:when, [], converted_params ++ guards}], {:__block__, [], converted_body}]}
      end
    end)

    {:fn, [line: normalize_line(line)], quoted_clauses}
  end

  defp binary_element_to_quoted({:bin_element, _line, {:string, _sline, value}, :default, :default}) do
    value
  end

  defp binary_element_to_quoted({:bin_element, _line, expr, size, type}) do
    quoted_expr = expression_to_quoted(expr)
    build_bin_element(quoted_expr, size, type)
  end

  defp build_bin_element(expr, :default, :default), do: expr
  defp build_bin_element(expr, size, :default) when is_integer(size), do: quote do: unquote(expr)::size(unquote(size))
  defp build_bin_element(expr, :default, type), do: quote do: unquote(expr)::unquote(type)
  defp build_bin_element(expr, size, type), do: quote do: unquote(expr)::size(unquote(size))-unquote(type)

  # List construction
  defp expression_to_quoted({:cons, line, head, {:nil, _}}) do
    quote line: normalize_line(line) do
      [unquote(expression_to_quoted(head))]
    end
  end

  defp expression_to_quoted({:cons, line, head, tail}) do
    quote line: normalize_line(line) do
      [unquote(expression_to_quoted(head)) | unquote(expression_to_quoted(tail))]
    end
  end

  defp expression_to_quoted({:nil, line}) do
    quote line: normalize_line(line) do
      []
    end
  end

  # Other expressions
  defp expression_to_quoted({:match, line, pattern, expr}) do
    quote line: normalize_line(line) do
      unquote(pattern_to_quoted(pattern)) = unquote(expression_to_quoted(expr))
    end
  end
  
  defp expression_to_quoted({:call, line, {:remote, _, mod, fun}, args}) do
    quoted_mod = expression_to_quoted(mod)
    quoted_fun = expression_to_quoted(fun)
    quoted_args = Enum.map(args, &expression_to_quoted/1)
    
    quote line: normalize_line(line) do
      unquote(quoted_mod).unquote(quoted_fun)(unquote_splicing(quoted_args))
    end
  end
  
  defp expression_to_quoted({:call, line, {:atom, _, fun}, args}) do
    quoted_args = Enum.map(args, &expression_to_quoted/1)
    quote line: normalize_line(line) do
      unquote(fun)(unquote_splicing(quoted_args))
    end
  end
  
  defp expression_to_quoted({:case, line, expr, clauses}) do
    quoted_expr = expression_to_quoted(expr)
    quoted_clauses = Enum.map(clauses, &clause_to_quoted/1)
    
    quote line: normalize_line(line) do
      case unquote(quoted_expr) do
        unquote(quoted_clauses)
      end
    end
  end
  
  defp expression_to_quoted({:block, line, exprs}) do
    quoted_exprs = Enum.map(exprs, &expression_to_quoted/1)
    quote line: normalize_line(line) do
      unquote_splicing(quoted_exprs)
    end
  end
  
  defp expression_to_quoted({:tuple, line, elements}) do
    quoted_elements = Enum.map(elements, &expression_to_quoted/1)
    quote line: normalize_line(line) do
      {unquote_splicing(quoted_elements)}
    end
  end

  # Operator expressions
  defp expression_to_quoted({:op, line, operator, left, right}) do
    quote line: normalize_line(line) do
      unquote({operator, [], [expression_to_quoted(left), expression_to_quoted(right)]})
    end
  end

  defp expression_to_quoted({:op, line, operator, operand}) do
    quote line: normalize_line(line) do
      unquote({operator, [], [expression_to_quoted(operand)]})
    end
  end
  
  # Literals and basic terms
  defp expression_to_quoted({:atom, line, value}) do
    quote line: normalize_line(line) do
      unquote(value)
    end
  end
  
  defp expression_to_quoted({:integer, line, value}) do
    quote line: normalize_line(line) do
      unquote(value)
    end
  end
  
  defp expression_to_quoted({:float, line, value}) do
    quote line: normalize_line(line) do
      unquote(value)
    end
  end
  
  defp expression_to_quoted({:string, line, value}) do
    quote line: normalize_line(line) do
      unquote(value)
    end
  end
  
  defp expression_to_quoted({:var, line, name}) do
    quote line: normalize_line(line) do
      unquote(Macro.var(name, nil))
    end
  end

  # Maps
  defp expression_to_quoted({:map, line, []}) do
    quote line: normalize_line(line) do
      %{}
    end
  end

  defp expression_to_quoted({:map, line, pairs}) do
    quoted_pairs = Enum.map(pairs, fn 
      {:map_field_assoc, _, key, value} -> 
        {:%{}, [], [{expression_to_quoted(key), expression_to_quoted(value)}]}
      {op, k, v} -> 
        {map_op_to_quoted(op), expression_to_quoted(k), expression_to_quoted(v)}
    end)
    quote line: normalize_line(line) do
      %{unquote_splicing(quoted_pairs)}
    end
  end

  # Helpers
  defp make_vars(n) when n > 0 do
    for i <- 1..n//1, do: Macro.var(:"arg#{i}", nil)
  end
  defp make_vars(_), do: []

  defp map_op_to_quoted(:exact), do: :%{}
  defp map_op_to_quoted(:assoc), do: :%{}

  defp convert_attribute_value(value) when is_atom(value) or is_integer(value) or is_float(value) or is_binary(value), do: value
  defp convert_attribute_value(value) when is_list(value), do: Enum.map(value, &convert_attribute_value/1)
  defp convert_attribute_value({a, b}), do: {convert_attribute_value(a), convert_attribute_value(b)}
  defp convert_attribute_value(other), do: other
end

Happy Hacking

I hope you enjoyed this silliness.