Skip to content

Commit 78bbaf8

Browse files
committed
Add Macro.postwalker/1
1 parent 6f27433 commit 78bbaf8

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
149149
* [Keyword] Add `Keyword.validate/2`
150150
* [Keyword] Implement `Keyword.filter/2` and `Keyword.map/2`
151151
* [List] Add `List.keyfind!/3`
152-
* [Macro] Add `Macro.prewalker/1`
152+
* [Macro] Add `Macro.prewalker/1` and `Macro.postwalker/1`
153153
* [Macro.Env] Add the following reflection functions: `required?/2`, `lookup_import/2`, `fetch_alias/2`, and `fetch_macro_alias/2`
154154
* [Map] Implement `Map.filter/2` and `Map.map/2`
155155
* [Module] Support `:nillify_clauses` in `Module.get_definition/3`

lib/elixir/lib/macro.ex

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,67 @@ defmodule Macro do
773773
prewalker(tail, fun.(head, acc), fun)
774774
end
775775

776+
@doc """
777+
Returns an enumerable that traverses the `ast` in depth-first,
778+
post-order traversal.
779+
780+
## Examples
781+
782+
iex> ast = quote do: foo(1, "abc")
783+
iex> Enum.map(Macro.postwalker(ast), & &1)
784+
[1, "abc", {:foo, [], [1, "abc"]}]
785+
786+
"""
787+
def postwalker(ast) do
788+
&postwalker([ast], make_ref(), &1, &2)
789+
end
790+
791+
defp postwalker(_buffer, _ref, {:halt, acc}, _fun) do
792+
{:halted, acc}
793+
end
794+
795+
defp postwalker(buffer, ref, {:suspend, acc}, fun) do
796+
{:suspended, acc, &postwalker(buffer, ref, &1, fun)}
797+
end
798+
799+
defp postwalker([], _ref, {:cont, acc}, _fun) do
800+
{:done, acc}
801+
end
802+
803+
defp postwalker([{ref, head} | tail], ref, {:cont, acc}, fun) do
804+
postwalker(tail, ref, fun.(head, acc), fun)
805+
end
806+
807+
defp postwalker([{left, right} = node | tail], ref, {:cont, acc}, fun) do
808+
postwalker([right, {ref, node} | tail], ref, fun.(left, acc), fun)
809+
end
810+
811+
defp postwalker([{left, meta, right} = node | tail], ref, {:cont, acc}, fun)
812+
when is_atom(left) and is_list(meta) do
813+
if is_atom(right) do
814+
postwalker(tail, ref, fun.(node, acc), fun)
815+
else
816+
postwalker(right ++ [{ref, node} | tail], ref, {:cont, acc}, fun)
817+
end
818+
end
819+
820+
defp postwalker([{left, meta, right} = node | tail], ref, cont_acc, fun)
821+
when is_list(meta) do
822+
if is_atom(right) do
823+
postwalker([left, {ref, node} | tail], ref, cont_acc, fun)
824+
else
825+
postwalker([left | right] ++ [{ref, node} | tail], ref, cont_acc, fun)
826+
end
827+
end
828+
829+
defp postwalker([list | tail], ref, cont_acc, fun) when is_list(list) do
830+
postwalker(list ++ [{ref, list} | tail], ref, cont_acc, fun)
831+
end
832+
833+
defp postwalker([head | tail], ref, {:cont, acc}, fun) do
834+
postwalker(tail, ref, fun.(head, acc), fun)
835+
end
836+
776837
@doc ~S"""
777838
Unescapes the given chars.
778839

lib/elixir/test/elixir/macro_test.exs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,13 +1024,41 @@ defmodule MacroTest do
10241024
{:five, [], MacroTest}
10251025
]
10261026

1027+
assert map == ast |> Macro.prewalk([], &{&1, [&1 | &2]}) |> elem(1) |> Enum.reverse()
10271028
assert Enum.zip(Macro.prewalker(ast), []) == Enum.zip(map, [])
10281029

10291030
for i <- 0..(length(map) + 1) do
10301031
assert Enum.take(Macro.prewalker(ast), i) == Enum.take(map, i)
10311032
end
10321033
end
10331034

1035+
test "postwalker/1" do
1036+
ast = quote do: :mod.foo(bar({1, 2}), [3, 4, five])
1037+
map = Enum.map(Macro.postwalker(ast), & &1)
1038+
1039+
assert map == [
1040+
:mod,
1041+
:foo,
1042+
{:., [], [:mod, :foo]},
1043+
1,
1044+
2,
1045+
{1, 2},
1046+
{:bar, [], [{1, 2}]},
1047+
3,
1048+
4,
1049+
{:five, [], MacroTest},
1050+
[3, 4, {:five, [], MacroTest}],
1051+
{{:., [], [:mod, :foo]}, [], [{:bar, [], [{1, 2}]}, [3, 4, {:five, [], MacroTest}]]}
1052+
]
1053+
1054+
assert map == ast |> Macro.postwalk([], &{&1, [&1 | &2]}) |> elem(1) |> Enum.reverse()
1055+
assert Enum.zip(Macro.postwalker(ast), []) == Enum.zip(map, [])
1056+
1057+
for i <- 0..(length(map) + 1) do
1058+
assert Enum.take(Macro.postwalker(ast), i) == Enum.take(map, i)
1059+
end
1060+
end
1061+
10341062
test "operator?/2" do
10351063
assert Macro.operator?(:+, 2)
10361064
assert Macro.operator?(:+, 1)

0 commit comments

Comments
 (0)