From 6bd613aee132467684eb2b882ad8aa85a0291827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gillet?= <jie.gillet@gmail.com> Date: Sat, 6 Nov 2021 18:10:49 +0900 Subject: [PATCH 1/2] Add _ to special variables --- lib/representer.ex | 4 ++-- test_data/special_variables/expected_representation.txt | 2 +- test_data/special_variables/input.ex | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/representer.ex b/lib/representer.ex index e478ade..f813a91 100644 --- a/lib/representer.ex +++ b/lib/representer.ex @@ -92,10 +92,10 @@ defmodule Representer do # variables # https://elixir-lang.org/getting-started/meta/quote-and-unquote.html # "The third element is either a list of arguments for the function call or an atom. When this element is an atom, it means the tuple represents a variable." - @special_var_names [:__CALLER__, :__DIR__, :__ENV__, :__MODULE__, :__STACKTRACE__, :...] + @special_var_names [:__CALLER__, :__DIR__, :__ENV__, :__MODULE__, :__STACKTRACE__, :..., :_] defp do_define_placeholders({atom, meta, context}, represented) when is_atom(atom) and is_nil(context) and atom not in @special_var_names do - {:ok, represented, mapped_term} = Representer.Mapping.get_placeholder(represented, atom) + {:ok, represented, mapped_term} = Mapping.get_placeholder(represented, atom) {{mapped_term, meta, context}, represented} end diff --git a/test_data/special_variables/expected_representation.txt b/test_data/special_variables/expected_representation.txt index 38f3525..2f2a03e 100644 --- a/test_data/special_variables/expected_representation.txt +++ b/test_data/special_variables/expected_representation.txt @@ -5,7 +5,7 @@ defmodule(Placeholder_1) do :math.pow(__DIR__) placeholder_6.(__ENV__) end - defmacro(placeholder_7()) do + defmacro(placeholder_7(_)) do placeholder_2(__CALLER__, __STACKTRACE__) placeholder_8 = 3 end diff --git a/test_data/special_variables/input.ex b/test_data/special_variables/input.ex index 5acff4a..36f6dc1 100644 --- a/test_data/special_variables/input.ex +++ b/test_data/special_variables/input.ex @@ -6,7 +6,7 @@ defmodule AnythingAndEverything do some_value.(__ENV__) end - defmacro foo() do + defmacro foo(_) do some_function(__CALLER__, __STACKTRACE__) _ignored = 3 end From b7e5ef13561511cd3c20abbba8a1fd075a91b186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gillet?= <jie.gillet@gmail.com> Date: Sat, 6 Nov 2021 18:13:46 +0900 Subject: [PATCH 2/2] Mulit-component modules, module aliases --- lib/representer.ex | 73 ++++++++++++------- lib/representer/mapping.ex | 13 +++- test/representer_test.exs | 3 + test_data/modules/expected_mapping.json | 16 ++++ test_data/modules/expected_representation.txt | 38 ++++++++++ test_data/modules/input.ex | 45 ++++++++++++ 6 files changed, 160 insertions(+), 28 deletions(-) create mode 100644 test_data/modules/expected_mapping.json create mode 100644 test_data/modules/expected_representation.txt create mode 100644 test_data/modules/input.ex diff --git a/lib/representer.ex b/lib/representer.ex index f813a91..28d369c 100644 --- a/lib/representer.ex +++ b/lib/representer.ex @@ -4,16 +4,18 @@ defmodule Representer do alias Representer.Mapping def process(file, code_output, mapping_output) do - {represented_ast, mapping} = represent(file) + {represented_ast, mapping} = + file + |> File.read!() + |> represent File.write!(code_output, Macro.to_string(represented_ast) <> "\n") File.write!(mapping_output, to_string(mapping)) end - def represent(file) do + def represent(code) do {ast, mapping} = - file - |> File.read!() + code |> Code.string_to_quoted!() |> Macro.prewalk(&add_meta/1) |> Macro.prewalk(Mapping.init(), &define_placeholders/2) @@ -49,17 +51,23 @@ defmodule Representer do do_define_placeholders(node, represented) end + # module definition defp do_define_placeholders( - {:defmodule, [line: x], - [{:__aliases__, [line: x], [module_name]} = module_alias | _] = args} = node, + {:defmodule, meta1, [{:__aliases__, meta2, module_name}, content]}, represented ) do - {:ok, represented, mapped_term} = Mapping.get_placeholder(represented, module_name, :module) - - module_alias = module_alias |> Tuple.delete_at(2) |> Tuple.append([mapped_term]) - args = [module_alias | args |> tl] - node = node |> Tuple.delete_at(2) |> Tuple.append(args) + {:ok, represented, names} = Mapping.get_placeholder(represented, module_name) + node = {:defmodule, meta1, [{:__aliases__, meta2, names}, content]} + {node, represented} + end + # module alias + defp do_define_placeholders( + {:alias, meta, [module, [as: {:__aliases__, meta2, module_alias}]]}, + represented + ) do + {:ok, represented, names} = Mapping.get_placeholder(represented, module_alias) + node = {:alias, meta, [module, [as: {:__aliases__, meta2, names}]]} {node, represented} end @@ -73,13 +81,13 @@ defmodule Representer do # function/macro/guard definition with a guard [{name3, meta3, args3} | args2_tail] = args2 - {:ok, represented, mapped_name} = Representer.Mapping.get_placeholder(represented, name3) + {:ok, represented, mapped_name} = Mapping.get_placeholder(represented, name3) meta2 = Keyword.put(meta2, :visited?, true) meta3 = Keyword.put(meta3, :visited?, true) {[{name, meta2, [{mapped_name, meta3, args3} | args2_tail]} | args_tail], represented} else - {:ok, represented, mapped_name} = Representer.Mapping.get_placeholder(represented, name) + {:ok, represented, mapped_name} = Mapping.get_placeholder(represented, name) meta2 = Keyword.put(meta2, :visited?, true) {[{mapped_name, meta2, args2} | args_tail], represented} @@ -114,10 +122,23 @@ defmodule Representer do do_use_existing_placeholders(node, represented) end + # module names + defp do_use_existing_placeholders({:__aliases__, meta, module_name}, represented) + when is_list(module_name) do + module_name = + Enum.map( + module_name, + &(Mapping.get_existing_placeholder(represented, &1) || &1) + ) + + meta = Keyword.put(meta, :visited?, true) + {{:__aliases__, meta, module_name}, represented} + end + # local function calls defp do_use_existing_placeholders({atom, meta, context}, represented) when is_atom(atom) and is_list(context) do - placeholder = Representer.Mapping.get_existing_placeholder(represented, atom) + placeholder = Mapping.get_existing_placeholder(represented, atom) # if there is no placeholder for this name, that means it's an imported or a standard library function/macro/special form atom = placeholder || atom @@ -127,20 +148,21 @@ defmodule Representer do # external function calls defp do_use_existing_placeholders( - {{:., meta2, [{:__aliases__, meta3, [module_name]}, function_name]}, meta, context}, + {{:., meta2, [{:__aliases__, _, module_name} = module, function_name]}, meta, context}, represented ) - when is_atom(module_name) and is_atom(function_name) do - placeholder_module_name = - Representer.Mapping.get_existing_placeholder(represented, module_name) + when is_list(module_name) and is_atom(function_name) do + {{_, _, new_module_name} = module, _} = do_use_existing_placeholders(module, represented) - module_name = placeholder_module_name || module_name + all_replaced? = + Enum.zip_with(module_name, new_module_name, &(&1 != &2)) + |> Enum.all?() placeholder_function_name = - if placeholder_module_name do - Representer.Mapping.get_existing_placeholder(represented, function_name) + if all_replaced? do + Mapping.get_existing_placeholder(represented, function_name) else - # hack: assuming that if a module has no placeholder name, that means it's not being defined in this file + # hack: assuming that if a module has no complete placeholder name, that means it's not being defined in this file # TODO: fix when dealing with aliases nil end @@ -148,10 +170,8 @@ defmodule Representer do function_name = placeholder_function_name || function_name meta2 = Keyword.put(meta2, :visited?, true) - meta3 = Keyword.put(meta3, :visited?, true) - {{{:., meta2, [{:__aliases__, meta3, [module_name]}, function_name]}, meta, context}, - represented} + {{{:., meta2, [module, function_name]}, meta, context}, represented} end # external function calls via __MODULE__ @@ -160,8 +180,7 @@ defmodule Representer do represented ) when is_atom(function_name) do - placeholder_function_name = - Representer.Mapping.get_existing_placeholder(represented, function_name) + placeholder_function_name = Mapping.get_existing_placeholder(represented, function_name) function_name = placeholder_function_name || function_name meta2 = Keyword.put(meta2, :visited?, true) diff --git a/lib/representer/mapping.ex b/lib/representer/mapping.ex index 056c8ae..80f06ef 100644 --- a/lib/representer/mapping.ex +++ b/lib/representer/mapping.ex @@ -27,7 +27,18 @@ defmodule Representer.Mapping do %Mapping{} end - def get_placeholder(%Mapping{} = map, term, _type \\ :term) do + def get_placeholder(%Mapping{} = map, terms) when is_list(terms) do + {map, names} = + Enum.reduce(terms, {map, []}, fn + name, {map, names} -> + {:ok, map, name} = Mapping.get_placeholder(map, name) + {map, [name | names]} + end) + + {:ok, map, Enum.reverse(names)} + end + + def get_placeholder(%Mapping{} = map, term) do if map.mappings[term] do {:ok, map, map.mappings[term]} else diff --git a/test/representer_test.exs b/test/representer_test.exs index 85576b3..3fb11a5 100644 --- a/test/representer_test.exs +++ b/test/representer_test.exs @@ -29,6 +29,9 @@ defmodule RepresenterTest do test "parentheses_in_pipes" do test_directory("parentheses_in_pipes") end + test "modules" do + test_directory("modules") + end end defp test_directory(dir) do diff --git a/test_data/modules/expected_mapping.json b/test_data/modules/expected_mapping.json new file mode 100644 index 0000000..678a492 --- /dev/null +++ b/test_data/modules/expected_mapping.json @@ -0,0 +1,16 @@ +{ + "Placeholder_1": "One", + "Placeholder_10": "Ten", + "Placeholder_11": "Eleven", + "Placeholder_12": "Twelve", + "Placeholder_13": "Thirteen", + "Placeholder_2": "Two", + "Placeholder_5": "Five", + "Placeholder_6": "Six", + "Placeholder_7": "Seven", + "Placeholder_8": "Eight", + "placeholder_14": "fourteen", + "placeholder_3": "three", + "placeholder_4": "four", + "placeholder_9": "nine" +} diff --git a/test_data/modules/expected_representation.txt b/test_data/modules/expected_representation.txt new file mode 100644 index 0000000..a9d4bf5 --- /dev/null +++ b/test_data/modules/expected_representation.txt @@ -0,0 +1,38 @@ +( + defmodule(Placeholder_1) do + alias(Placeholder_1, as: Placeholder_2) + def(placeholder_3) do + Placeholder_1.placeholder_4() + Placeholder_2.placeholder_4() + end + def(placeholder_4) do + :ok + end + end + defmodule(Placeholder_5.Placeholder_6.Placeholder_7) do + alias(Placeholder_5.Placeholder_6, as: Placeholder_8) + def(placeholder_9) do + Placeholder_1.placeholder_3() + Placeholder_2.placeholder_3() + Placeholder_5.Placeholder_6.Placeholder_7.placeholder_9() + Placeholder_8.Placeholder_7.placeholder_9() + __MODULE__.placeholder_9() + External.external() + Placeholder_1.External.external() + Placeholder_5.External.Placeholder_7.nine() + end + defmodule(Placeholder_10) do + + end + defmodule(Placeholder_11) do + alias(Placeholder_5.Placeholder_6.Placeholder_7.{Placeholder_10, Placeholder_11}) + alias(Placeholder_8.Placeholder_7.Placeholder_11, as: Placeholder_12) + alias(Placeholder_8.Placeholder_7, as: Placeholder_2) + alias(External, as: Placeholder_13) + def(placeholder_14) do + External.external() + Placeholder_13.external() + end + end + end +) diff --git a/test_data/modules/input.ex b/test_data/modules/input.ex new file mode 100644 index 0000000..3804af9 --- /dev/null +++ b/test_data/modules/input.ex @@ -0,0 +1,45 @@ +defmodule One do + alias One, as: Two + + def three do + One.four() + Two.four() + end + + def four, do: :ok +end + +defmodule Five.Six.Seven do + alias Five.Six, as: Eight + + def nine do + One.three() + Two.three() + + Five.Six.Seven.nine() + Eight.Seven.nine() + __MODULE__.nine() + # not replaced + External.external() + # only :One is replaced + One.External.external() + # nine not replaced because of External + Five.External.Seven.nine() + end + + defmodule Ten do + end + + defmodule Eleven do + alias Five.Six.Seven.{Ten, Eleven} + alias Eight.Seven.Eleven, as: Twelve + alias Eight.Seven, as: Two + + alias External, as: Thirteen + + def fourteen do + External.external() + Thirteen.external() + end + end +end