Skip to content

Commit

Permalink
Merge pull request #7 from inaka/jfacorro.4.implement.zipper
Browse files Browse the repository at this point in the history
[#4] Implement zipper
  • Loading branch information
elbrujohalcon committed Sep 8, 2014
2 parents 7b4af74 + 6670f22 commit 196b1cb
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 143 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ ERLC_OPTS += +warn_export_vars +warn_exported_vars +warn_missing_spec +warn_unty
# Commont Test Config

CT_SUITES = zipper
CT_OPTS = -cover test/zipper.coverspec

shell: app
erl -pa ebin -pa deps/*/ebin -s sync

tests-shell: app build-ct-suites
erl -pa ebin -pa deps/*/ebin -pa test -s sync
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Root = #{type => planet,
You can build a zipper by providing three simple functions:
- `IsBranchFun`: takes a node and returns `true` if it is a branch node or
`false` otherwise.
- `ChildrenFun`: takes a node and returns a list of its nodes.
- `ChildrenFun`: takes a node and returns a list of its children.
- `MakeNodeFun`: takes a node and a list of children and returns a new node
containg the supplied list as children.

Expand All @@ -50,6 +50,10 @@ the map tree structure above:

```erlang
%% Create the zipper
IsBranchFun = fun
(#{children := [_ | _]) -> true;
(_) -> false
end,
IsBranchFun = fun is_map/1,
ChildrenFun = fun(Node) -> maps:get(children, Node) end,
MakeNodeFun = fun(Node, Children) -> Node#{children => Children} end,
Expand Down
2 changes: 1 addition & 1 deletion erlang.mk
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ else
endif

TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard
TEST_ERLC_OPTS += -DTEST=1 -DEXTRA=1 +'{parse_transform, eunit_autoexport}'
TEST_ERLC_OPTS += -DTEST=1 -DEXTRA=1

# Core targets.

Expand Down
150 changes: 124 additions & 26 deletions src/zipper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
left/1,
right/1,
next/1,
is_end/1,
prev/1,
root/1,
traverse/2,
%% Info
node/1,
children/1
children/1,
is_branch/1
]).

-type zipper() ::
Expand All @@ -24,45 +26,141 @@
-type operation() :: next | prev | up | down | left | right | root.

-spec new(fun(), fun(), fun(), term()) -> zipper().
new(_IsBranch, _Children, _MakeNode, _Root) ->
#{}.
new(IsBranch, Children, MakeNode, Root) ->
#{spec => #{is_branch => IsBranch,
children => Children,
make_node => MakeNode
},
node => Root,
info => #{lefts => [],
rights => [],
parent_node => undefined,
parent_info => undefined
}
}.

-spec up(zipper()) -> zipper().
up(_Zipper) ->
#{}.
-spec up(zipper()) -> zipper() | undefined.
up(#{info := #{parent_node := undefined}}) ->
undefined;
up(Zipper = #{info := #{parent_node := Parent,
parent_info := ParentInfo}}) ->
Zipper#{node => Parent,
info => ParentInfo}.

-spec down(zipper()) -> zipper().
down(_Zipper) ->
#{}.
-spec down(zipper()) -> zipper() | undefined.
down(Zipper = #{node := Node,
info := Info,
spec := #{children := Children}}) ->
case is_branch(Zipper) of
true ->
[NewNode | Rights] = Children(Node),
Zipper#{node => NewNode,
info => #{lefts => [],
rights => Rights,
parent_node => Node,
parent_info => Info
}
};
false ->
undefined
end.

-spec left(zipper()) -> zipper().
left(_Zipper) ->
#{}.
left(#{info := #{lefts := []}}) ->
undefined;
left(Zipper = #{info := Info = #{lefts := [NewNode | Lefts],
rights := Rights},
node := Node}) ->
Zipper#{info => Info#{lefts => Lefts,
rights => [Node | Rights]},
node => NewNode
}.

-spec right(zipper()) -> zipper().
right(_Zipper) ->
#{}.
right(#{info := #{rights := []}}) ->
undefined;
right(Zipper = #{info := Info = #{rights := [NewNode | Rights],
lefts := Lefts},
node := Node}) ->
Zipper#{info => Info#{rights := Rights,
lefts := [Node | Lefts]},
node := NewNode
}.

-spec next(zipper()) -> zipper().
next(_Zipper) ->
#{}.
next(Zipper = #{info := 'end'}) ->
Zipper;
next(Zipper) ->
case {is_branch(Zipper), right(Zipper)} of
{true, _} -> down(Zipper);
{false, undefined} -> next_recur(Zipper);
{false, Right} -> Right
end.

-spec next_recur(zipper()) -> zipper().
next_recur(Zipper) ->
case up(Zipper) of
undefined -> Zipper#{info => 'end'};
UpZipper ->
case right(UpZipper) of
undefined -> next_recur(UpZipper);
Next -> Next
end
end.

-spec is_end(zipper()) -> boolean().
is_end(#{info := 'end'}) ->
true;
is_end(_Zipper) ->
false.

-spec prev(zipper()) -> zipper().
prev(_Zipper) ->
#{}.
prev(Zipper = #{info := #{lefts := []}}) ->
up(Zipper);
prev(Zipper) ->
prev_recur(left(Zipper)).

prev_recur(Zipper) ->
case down(Zipper) of
undefined -> Zipper;
DownZipper ->
RightMost = rightmost(DownZipper),
prev_recur(RightMost)
end.

rightmost(Zipper = #{info := Info = #{rights := Rights,
lefts := Lefts},
node := Node}) ->
Fun = fun(Item, Acc) -> [Item | Acc] end,
[NewNode | NewLefts] = lists:foldl(Fun, [Node | Lefts], Rights),
Zipper#{info => Info#{lefts => NewLefts,
rights => []},
node => NewNode}.

-spec root(zipper()) -> zipper().
root(_Zipper) ->
#{}.
root(Zipper) ->
case up(Zipper) of
undefined -> zipper:node(Zipper);
Parent -> root(Parent)
end.

-spec traverse([operation()], zipper()) -> zipper().
traverse([], Zipper) ->
Zipper;
traverse([Op | Rest], Zipper) ->
traverse(Rest, zipper:Op(Zipper)).

-spec node(zipper()) -> zipper().
node(_Zipper) ->
#{}.
node(#{node := Node}) ->
Node.

-spec children(zipper()) -> zipper().
children(_Zipper) ->
#{}.
children(Zipper = #{spec := #{children := Children}, node := Node}) ->
case is_branch(Zipper) of
true -> Children(Node);
false -> throw(children_on_leaf)
end.

-spec traverse([operation()], zipper()) -> zipper().
traverse(_Operations, _Zipper) ->
#{}.
-spec is_branch(zipper()) -> boolean().
is_branch(#{spec := #{is_branch := IsBranch}, node := Node}) ->
IsBranch(Node).
7 changes: 7 additions & 0 deletions test/zipper.coverspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
%% Specific modules to include in cover.
{
incl_mods,
[
zipper
]
}.
Loading

0 comments on commit 196b1cb

Please sign in to comment.