Skip to content

Commit 4fc23d0

Browse files
committed
Add Macro.prewalker/1
1 parent 23bb088 commit 4fc23d0

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

lib/elixir/lib/macro.ex

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,62 @@ defmodule Macro do
717717

718718
defp find_invalid(other), do: {:error, other}
719719

720+
@doc """
721+
Returns an enumerable that traverses the `ast` in depth-first,
722+
pre-order traversal.
723+
724+
## Examples
725+
726+
iex> ast = quote do: foo(1, "abc")
727+
iex> Enum.map(Macro.prewalker(ast), & &1)
728+
[{:foo, [], [1, "abc"]}, 1, "abc"]
729+
730+
"""
731+
def prewalker(ast) do
732+
&prewalker([ast], &1, &2)
733+
end
734+
735+
defp prewalker(_buffer, {:halt, acc}, _fun) do
736+
{:halted, acc}
737+
end
738+
739+
defp prewalker(buffer, {:suspend, acc}, fun) do
740+
{:suspended, acc, &prewalker(buffer, &1, fun)}
741+
end
742+
743+
defp prewalker([], {:cont, acc}, _fun) do
744+
{:done, acc}
745+
end
746+
747+
defp prewalker([{left, right} = node | tail], {:cont, acc}, fun) do
748+
prewalker([left, right | tail], fun.(node, acc), fun)
749+
end
750+
751+
defp prewalker([{left, meta, right} = node | tail], {:cont, acc}, fun)
752+
when is_atom(left) and is_list(meta) do
753+
if is_atom(right) do
754+
prewalker(tail, fun.(node, acc), fun)
755+
else
756+
prewalker(right ++ tail, fun.(node, acc), fun)
757+
end
758+
end
759+
760+
defp prewalker([{left, meta, right} = node | tail], {:cont, acc}, fun) when is_list(meta) do
761+
if is_atom(right) do
762+
prewalker([left | tail], fun.(node, acc), fun)
763+
else
764+
prewalker([left | right] ++ tail, fun.(node, acc), fun)
765+
end
766+
end
767+
768+
defp prewalker([list | tail], {:cont, acc}, fun) when is_list(list) do
769+
prewalker(list ++ tail, fun.(list, acc), fun)
770+
end
771+
772+
defp prewalker([head | tail], {:cont, acc}, fun) do
773+
prewalker(tail, fun.(head, acc), fun)
774+
end
775+
720776
@doc ~S"""
721777
Unescapes the given chars.
722778

lib/elixir/test/elixir/macro_test.exs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,32 @@ defmodule MacroTest do
10051005
assert Macro.struct!(StructBang, __ENV__) == %{__struct__: StructBang, a: nil, b: nil}
10061006
end
10071007

1008+
test "prewalker/1" do
1009+
ast = quote do: :mod.foo(bar({1, 2}), [3, 4, five])
1010+
map = Enum.map(Macro.prewalker(ast), & &1)
1011+
1012+
assert map == [
1013+
{{:., [], [:mod, :foo]}, [], [{:bar, [], [{1, 2}]}, [3, 4, {:five, [], MacroTest}]]},
1014+
{:., [], [:mod, :foo]},
1015+
:mod,
1016+
:foo,
1017+
{:bar, [], [{1, 2}]},
1018+
{1, 2},
1019+
1,
1020+
2,
1021+
[3, 4, {:five, [], MacroTest}],
1022+
3,
1023+
4,
1024+
{:five, [], MacroTest}
1025+
]
1026+
1027+
assert Enum.zip(Macro.prewalker(ast), []) == Enum.zip(map, [])
1028+
1029+
for i <- 0..(length(map) + 1) do
1030+
assert Enum.take(Macro.prewalker(ast), i) == Enum.take(map, i)
1031+
end
1032+
end
1033+
10081034
test "operator?/2" do
10091035
assert Macro.operator?(:+, 2)
10101036
assert Macro.operator?(:+, 1)

0 commit comments

Comments
 (0)