Skip to content

Commit

Permalink
Merge pull request #14 from hrzndhrn/issue/13
Browse files Browse the repository at this point in the history
Refactor normalizer
  • Loading branch information
NickNeck committed Jul 11, 2023
2 parents e783796 + 440bf6a commit 0f666a8
Show file tree
Hide file tree
Showing 16 changed files with 1,125 additions and 31 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
name: Test on Ubuntu (Elixir ${{ matrix.elixir }}, OTP ${{ matrix.otp }})
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
elixir:
- '1.13.4'
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.5.3 - 2023/07/11

+ The `BeamFile.Normalizer` generates now different AST formats for
`BeamFile.elixir_code/2` and `BeamFile.elixir_quoted/2`.

## 0.5.2 - 2023/07/08

+ Fix `BeamFile.Normalizer`
Expand Down
2 changes: 1 addition & 1 deletion lib/beam_file.ex
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ defmodule BeamFile do
if docs do
DebugInfo.code(debug_info, docs)
else
debug_info |> DebugInfo.ast() |> Macro.to_string()
debug_info |> DebugInfo.ast(:code) |> Macro.to_string()
end

{:ok, code}
Expand Down
Empty file removed lib/beam_file/ast.ex
Empty file.
7 changes: 4 additions & 3 deletions lib/beam_file/debug_info.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ defmodule BeamFile.DebugInfo do

@default_lang "en"

def ast(%{module: module} = debug_info) do
def ast(%{module: module} = debug_info, target \\ :ast) do
ast = debug_info |> definitions(:desc) |> Enum.map(fn {_name, _arity, ast} -> ast end)

Normalizer.normalize(
{:defmodule, [context: Elixir, import: Kernel],
[
{:__aliases__, [alias: false], [module]},
[do: {:__block__, [], ast}]
]}
]},
target
)
end

Expand Down Expand Up @@ -87,7 +88,7 @@ defmodule BeamFile.DebugInfo do

defp defs_to_code([{name, arity, ast} | defs], defdocs, acc) do
{defdoc, defdocs} = defdoc(name, arity, defdocs)
code = ast |> Normalizer.normalize() |> Macro.to_string()
code = ast |> Normalizer.normalize(:code) |> Macro.to_string()

defs_to_code(defs, defdocs, [defdoc, code | acc])
end
Expand Down
50 changes: 29 additions & 21 deletions lib/beam_file/normalizer.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
defmodule BeamFile.Normalizer do
@moduledoc false

alias Code.Normalizer

@chars Enum.to_list(?a..?z) ++ Enum.to_list(?A..?Z) ++ Enum.to_list(?0..?9) ++ [??, ?!, ?_]
@none_unquotes [
:%{},
Expand Down Expand Up @@ -57,28 +55,34 @@ defmodule BeamFile.Normalizer do
:~>>
]

def normalize(block) when is_list(block), do: Enum.map(block, &normalize/1)
def normalize(block, target) when is_list(block) do
if Keyword.keyword?(block) do
Enum.map(block, fn {key, value} -> {key, normalize(value, target)} end)
else
Enum.map(block, fn expr -> normalize(expr, target) end)
end
end

def normalize({:do, block}) do
{:do, normalize(block)}
def normalize({:do, block}, target) do
{:do, normalize(block, target)}
end

def normalize({a, b}), do: {normalize(a), normalize(b)}
def normalize({a, b}, target), do: {normalize(a, target), normalize(b, target)}

def normalize({:.., meta, args}) do
def normalize({:.., meta, args}, _target) do
case args do
Elixir -> {:"(..)", meta, args}
[{a, _, nil}, {b, _, nil}] -> {String.to_atom("#{a}..#{b}"), meta, args}
end
end

def normalize({:super, meta, args}) do
def normalize({:super, meta, args}, target) do
case Keyword.has_key?(meta, :super) do
true ->
{{_kind, name}, meta} = Keyword.pop!(meta, :super)

if unquote?(name) do
{{:unquote, meta, [name]}, meta, normalize(args)}
{{:unquote, meta, [name]}, meta, normalize(args, target)}
else
{name, meta, args}
end
Expand All @@ -88,18 +92,22 @@ defmodule BeamFile.Normalizer do
end
end

def normalize({{:., meta1, [:erlang, :++]}, meta2, [left, right]}) do
left = Normalizer.normalize(left)
right = Normalizer.normalize(right)

{{:., meta1, [:erlang, :++]}, meta2, [left, right]}
def normalize({{:., meta1, [:erlang, :++]}, meta2, [left, right] = args}, :code = target) do
if (Keyword.keyword?(left) and Keyword.has_key?(left, :do)) or
(Keyword.keyword?(right) and Keyword.has_key?(right, :do)) do
left = left |> normalize(target) |> Code.Normalizer.normalize()
right = right |> normalize(target) |> Code.Normalizer.normalize()
{{:., meta1, [:erlang, :++]}, meta2, [left, right]}
else
{{:., meta1, [:erlang, :++]}, meta2, normalize(args, target)}
end
end

def normalize({{:., meta1, [:erlang, :binary_to_atom]}, meta2, [arg, :utf8]}) do
def normalize({{:., meta1, [:erlang, :binary_to_atom]}, meta2, [arg, :utf8]}, _target) do
{{:., meta1, [:erlang, :binary_to_atom]}, meta2, [arg, {:__block__, [], [:utf8]}]}
end

def normalize({:for, meta, args}) when is_list(args) do
def normalize({:for, meta, args}, target) when is_list(args) do
{args, [last]} = Enum.split(args, -1)

case Keyword.keyword?(last) do
Expand All @@ -108,23 +116,23 @@ defmodule BeamFile.Normalizer do
block = Keyword.fetch!(last, :do)
last = last |> Keyword.delete(:do) |> Enum.concat([{:do, block}])

args = normalize(args ++ [last])
args = normalize(args ++ [last], target)
{:for, meta, args}

false ->
{:for, meta, args}
end
end

def normalize({expr, meta, args}) do
def normalize({expr, meta, args}, target) do
if unquote?(expr) do
{{:unquote, [], [expr]}, meta, normalize(args)}
{{:unquote, [], [expr]}, meta, normalize(args, target)}
else
{expr, meta, normalize(args)}
{expr, meta, normalize(args, target)}
end
end

def normalize(block), do: block
def normalize(block, _target), do: block

defp unquote?(atom) when atom in @none_unquotes, do: false
defp unquote?(atom) when is_atom(atom), do: atom |> to_string() |> unquote?()
Expand Down
10 changes: 5 additions & 5 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule BeamFile.MixProject do
def project do
[
app: :beam_file,
version: "0.5.2",
version: "0.5.3",
elixir: "~> 1.13",
description: "An interface to the BEAM file format and a decompiler",
start_permanent: Mix.env() == :prod,
Expand Down Expand Up @@ -57,10 +57,10 @@ defmodule BeamFile.MixProject do

defp deps do
[
{:credo, "~> 1.5", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.1", only: :dev, runtime: false},
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
{:excoveralls, "~> 0.14", only: :test},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.3", only: :dev, runtime: false},
{:ex_doc, "~> 0.30", only: :dev, runtime: false},
{:excoveralls, "~> 0.16", only: :test},
{:recode, "~> 0.5.1", only: [:dev, :test]}
]
end
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"},
"earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_doc": {:hex, :ex_doc, "0.30.1", "a0f3b598d3c2cb3af48af39e59fa66ac8d4033740409b11dd753a3f30f8f8f7a", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2e2216e84aa33e5803f8898d762b0f5e76bf2de3a08d1f40ac5f74456dd5057c"},
"ex_doc": {:hex, :ex_doc, "0.30.2", "7a3e63ddb387746925bbbbcf6e9cb00e43c757cc60359a2b40059aea573e3e57", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5ba8cb61d069012f16b50e575b0e3e6cf4083935f7444fab0d92c9314ce86bb6"},
"excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"glob_ex": {:hex, :glob_ex, "0.1.4", "fc69cb3f6df9138a1e36e9aa041ef2eab0d4dfe916331425f6bac290d1977e79", [:mix], [], "hexpm", "583d35559dc5b17f14612f7153aaaf6dcc13edf2e383126e2dfb5f2d19c78b89"},
Expand Down
12 changes: 12 additions & 0 deletions test/beam_file_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,18 @@ defmodule BeamFileTest do
TestSupport.fixture("math_without_docs.exs")
end

test "returns the elixir ast for Comps" do
assert quoted = BeamFile.elixir_quoted!(Comps)
assert quoted == TestSupport.fixture("comps_ast.exs", eval: true)

{:defmodule, meta, [_ | block]} = quoted

assert [{TestComps, _bin}] =
Code.compile_quoted(
{:defmodule, meta, [{:__aliases__, [alias: false], [TestComps]} | block]}
)
end

test "returns the elixir ast for MultiWhen" do
assert BeamFile.elixir_quoted!(MultiWhen)
end
Expand Down
55 changes: 55 additions & 0 deletions test/fixtures/1.13.4/comps.exs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,61 @@ defmodule Elixir.Comps do
do: (acc -> Map.update(acc, <<x>>, 1, fn x1 -> :erlang.+(x1, 1) end))
end

def seven do
:erlang.++(
for x <- [1, 2] do
x
end,
for y <- [3, 4] do
y
end
)
end

def eight do
:erlang.--(
for x <- [1, 2] do
x
end,
for y <- [3, 4] do
y
end
)
end

def nine(list) do
:erlang.++(
list,
Enum.sort(
for x <- [1, 2] do
x
end
)
)
end

def ten(list) do
:erlang.++(
list,
Enum.sort(
for x <- [1, 2] do
x
end
)
)
end

def eleven(list) do
:erlang.--(
list,
Enum.sort(
for x <- [1, 2] do
x
end
)
)
end

def users(users) do
for {type, name} when :erlang."/="(type, :guest) <- users do
String.upcase(name)
Expand Down
Loading

0 comments on commit 0f666a8

Please sign in to comment.