diff --git a/lib/sourceror/zipper.ex b/lib/sourceror/zipper.ex index bc92e12..9c5a8fa 100644 --- a/lib/sourceror/zipper.ex +++ b/lib/sourceror/zipper.ex @@ -726,6 +726,29 @@ defmodule Sourceror.Zipper do end end + @doc """ + Returns a list of `zippers` to each `node` that satisfies the `predicate` function, or + an empty list if none are found. + + The optional second parameters specifies the `direction`, defaults to + `:next`. + """ + @spec find_all(t, direction :: :prev | :next, predicate :: (tree -> any)) :: t | [] + def find_all(%Z{} = zipper, direction \\ :next, predicate) + when direction in [:next, :prev] and is_function(predicate, 1) do + do_find_all(zipper, move(direction), predicate, []) + end + + defp do_find_all(nil, _move, _predicate, buffer), do: Enum.reverse(buffer) + + defp do_find_all(%Z{node: tree} = zipper, move, predicate, buffer) do + if predicate.(tree) do + zipper |> move.() |> do_find_all(move, predicate, [zipper | buffer]) + else + zipper |> move.() |> do_find_all(move, predicate, buffer) + end + end + @spec find_value(t, (tree -> any)) :: any | nil def find_value(%Z{} = zipper, direction \\ :next, fun) when direction in [:next, :prev] and is_function(fun, 1) do diff --git a/test/zipper_test.exs b/test/zipper_test.exs index c674cb5..8d9171a 100644 --- a/test/zipper_test.exs +++ b/test/zipper_test.exs @@ -571,6 +571,72 @@ defmodule SourcerorTest.ZipperTest do end end + describe "find_all/3" do + test "finds zippers with a predicate" do + zipper = Z.zip([1, [2, [3, 4], 5]]) + + assert Z.find_all(zipper, fn + x when is_integer(x) -> rem(x, 2) == 0 + _other -> false + end) + |> Enum.map(&(Z.node(&1))) == [2, 4] + + assert Z.find_all(zipper, :next, fn + x when is_integer(x) -> rem(x, 2) == 0 + _other -> false + end) + |> Enum.map(&(Z.node(&1))) == [2, 4] + end + + test "returns empty list if nothing was found" do + zipper = Z.zip([1, [2, [3, 4], 5]]) + + assert Z.find_all(zipper, fn + x when is_integer(x) -> x == 9 + _other -> false + end) == [] + + assert Z.find_all(zipper, :prev, fn + x when is_integer(x) -> x == 9 + _other -> false + end) == [] + end + + test "finds a zippers with a predicate in direction :prev" do + zipper = + [1, [2, [3, 4], 5]] + |> Z.zip() + |> Z.next() + |> Z.next() + |> Z.next() + |> Z.next() + |> Z.next() + |> Z.next() + |> Z.next() + + assert Z.find_all(zipper, :prev, fn + x when is_integer(x) -> rem(x, 2) == 0 + _other -> false + end) + |> Enum.map(&(Z.node(&1))) == [4, 2] + end + + test "retruns empty list if nothing was found in direction :prev" do + zipper = + [1, [2, [3, 4], 5]] + |> Z.zip() + |> Z.next() + |> Z.next() + |> Z.next() + |> Z.next() + |> Z.next() + |> Z.next() + |> Z.next() + + assert Z.find_all(zipper, :prev, fn x -> x == 9 end) == [] + end + end + describe "find_value/3" do test "finds a zipper and returns the value" do zipper = Z.zip([1, [2, [3, 4], 5]])