Skip to content

Commit

Permalink
Add support for Zipper.find_all/3
Browse files Browse the repository at this point in the history
  • Loading branch information
bcardarella committed Aug 10, 2024
1 parent 6027a3b commit 94acb03
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 0 deletions.
23 changes: 23 additions & 0 deletions lib/sourceror/zipper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 66 additions & 0 deletions test/zipper_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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]])
Expand Down

0 comments on commit 94acb03

Please sign in to comment.