fr 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +0 -0
- data/Rakefile +81 -0
- data/lib/fr.rb +44 -0
- data/lib/fr/applicative.rb +0 -0
- data/lib/fr/array.rb +168 -0
- data/lib/fr/boolean.rb +15 -0
- data/lib/fr/either.rb +94 -0
- data/lib/fr/errors.rb +5 -0
- data/lib/fr/errors/zipper_error.rb +8 -0
- data/lib/fr/functor.rb +11 -0
- data/lib/fr/functor/array.rb +15 -0
- data/lib/fr/functor/either.rb +17 -0
- data/lib/fr/functor/maybe.rb +17 -0
- data/lib/fr/maybe.rb +76 -0
- data/lib/fr/monad.rb +236 -0
- data/lib/fr/monad/array.rb +21 -0
- data/lib/fr/monad/either.rb +18 -0
- data/lib/fr/monad/maybe.rb +24 -0
- data/lib/fr/monad/random.rb +50 -0
- data/lib/fr/monad/reader.rb +45 -0
- data/lib/fr/monad/state.rb +62 -0
- data/lib/fr/monad/writer.rb +49 -0
- data/lib/fr/monoid.rb +52 -0
- data/lib/fr/monoid/array.rb +13 -0
- data/lib/fr/monoid/boolean.rb +23 -0
- data/lib/fr/monoid/either.rb +13 -0
- data/lib/fr/monoid/maybe.rb +15 -0
- data/lib/fr/monoid/numeric.rb +31 -0
- data/lib/fr/monoid/string.rb +11 -0
- data/lib/fr/numeric.rb +1 -0
- data/lib/fr/object.rb +45 -0
- data/lib/fr/string.rb +1 -0
- data/lib/fr/szipper.rb +36 -0
- data/lib/fr/szipper/abstract_cursor.rb +63 -0
- data/lib/fr/szipper/edited_cursor.rb +62 -0
- data/lib/fr/szipper/enumerable.rb +266 -0
- data/lib/fr/szipper/head_cursor.rb +71 -0
- data/lib/fr/szipper/head_prev_cursor.rb +112 -0
- data/lib/fr/szipper/memoized_cursor.rb +59 -0
- data/lib/fr/szipper/node.rb +67 -0
- data/lib/fr/szipper/tail_next_cursor.rb +68 -0
- data/lib/fr/thunk.rb +73 -0
- data/lib/fr/tzipper.rb +38 -0
- data/lib/fr/tzipper/abstract_cursor.rb +351 -0
- data/lib/fr/tzipper/dangling_cursor.rb +107 -0
- data/lib/fr/tzipper/edited_cursor.rb +161 -0
- data/lib/fr/tzipper/memoized_cursor.rb +129 -0
- data/lib/fr/tzipper/node.rb +57 -0
- data/lib/fr/tzipper/path.rb +125 -0
- data/lib/fr/tzipper/root_cursor.rb +124 -0
- data/lib/fr/unfold.rb +93 -0
- data/spec/examples/array.example +0 -0
- data/spec/examples/monads/array.example +0 -0
- data/spec/examples/monads/maybe.example +0 -0
- data/spec/examples/monads/random.example +0 -0
- data/spec/examples/monads/state.example +0 -0
- data/spec/examples/szipper.example +652 -0
- data/spec/examples/thunk.example +0 -0
- data/spec/examples/tzipper.example +0 -0
- data/spec/examples/unfold.example +90 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/szipper/model.rb +225 -0
- data/spec/support/szipper/nodel.rb +144 -0
- metadata +116 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module Fr
|
2
|
+
module SZipper
|
3
|
+
|
4
|
+
class MemoizedCursor < AbstractCursor
|
5
|
+
# @return [#next, #tail?]
|
6
|
+
attr_reader :node
|
7
|
+
|
8
|
+
def initialize(node, prev)
|
9
|
+
@node, @prev =
|
10
|
+
node, prev
|
11
|
+
end
|
12
|
+
|
13
|
+
def prev(phantom = false)
|
14
|
+
@prev
|
15
|
+
end
|
16
|
+
|
17
|
+
def head?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def tail?
|
22
|
+
@node.tail?
|
23
|
+
end
|
24
|
+
|
25
|
+
def append(node)
|
26
|
+
EditedCursor.new(node.copy(next: @node.next), self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def prepend(node)
|
30
|
+
EditedCursor.new(node.copy(next: @node), @prev)
|
31
|
+
end
|
32
|
+
|
33
|
+
def update(node = nil)
|
34
|
+
if node.nil?
|
35
|
+
EditedCursor.new(yield(@node).copy(next: @node.next), @prev)
|
36
|
+
else
|
37
|
+
EditedCursor.new(node.copy(next: @node.next), @prev)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def replace(node)
|
42
|
+
EditedCursor.new(node, @prev)
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete
|
46
|
+
if @prev.head?
|
47
|
+
HeadCursor.new(@prev.node.copy(next: @node.next))
|
48
|
+
else
|
49
|
+
EditedCursor.new(@prev.node.copy(next: @node.next), @prev.prev)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
"Cursor([..., #{@node.inspect}, ...])"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Fr
|
2
|
+
module SZipper
|
3
|
+
|
4
|
+
# Example implementation for a stream node. For the
|
5
|
+
# purposes of traversal with a zipper, nodes can be
|
6
|
+
# anything that implements #copy, #next, and #tail?
|
7
|
+
#
|
8
|
+
# You'll probably want access to some datum too, like
|
9
|
+
# #value. Otherwise you'll be traversing a stream of
|
10
|
+
# otherwise useless cells.
|
11
|
+
#
|
12
|
+
class Node
|
13
|
+
attr_reader :value
|
14
|
+
|
15
|
+
attr_reader :next
|
16
|
+
|
17
|
+
def initialize(value, _next = nil)
|
18
|
+
@value, @next =
|
19
|
+
value, _next
|
20
|
+
end
|
21
|
+
|
22
|
+
def tail?
|
23
|
+
@next.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def copy(options = {})
|
27
|
+
Node.new \
|
28
|
+
options.fetch(:value, @value),
|
29
|
+
options.fetch(:next, @next)
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
"Node(#{@value})"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class << Node
|
38
|
+
def cons(value, _next)
|
39
|
+
Node.new(value, _next)
|
40
|
+
end
|
41
|
+
|
42
|
+
def tail(value)
|
43
|
+
Node.new(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# Convenient constructor to build a chain of Nodes
|
48
|
+
# from any "sequential" enum. That excludes things
|
49
|
+
# like Hash, Set, and Range because their traversal
|
50
|
+
# order isn't deterministic. Otherwise, anything that
|
51
|
+
# implements #reverse and #inject can be converted.
|
52
|
+
#
|
53
|
+
def build(enum)
|
54
|
+
if enum.empty?
|
55
|
+
raise Errors::ZipperError,
|
56
|
+
"enum is empty"
|
57
|
+
end
|
58
|
+
|
59
|
+
tail, *rest = enum.reverse
|
60
|
+
rest.inject(tail(tail)) do |_next, value|
|
61
|
+
cons(value, _next)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Fr
|
2
|
+
module SZipper
|
3
|
+
|
4
|
+
class TailNextCursor < AbstractCursor
|
5
|
+
def initialize(prev)
|
6
|
+
@prev = prev
|
7
|
+
end
|
8
|
+
|
9
|
+
def empty?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def tail?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def head?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def tail
|
22
|
+
@prev
|
23
|
+
end
|
24
|
+
|
25
|
+
def prev(phantom = false)
|
26
|
+
@prev
|
27
|
+
end
|
28
|
+
|
29
|
+
def next(phantom = false)
|
30
|
+
if phantom
|
31
|
+
self
|
32
|
+
else
|
33
|
+
raise Errors::ZipperError,
|
34
|
+
"tail node has no next"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def append(node)
|
39
|
+
@prev.append(node)
|
40
|
+
end
|
41
|
+
|
42
|
+
def prepend(node)
|
43
|
+
@prev.append(node)
|
44
|
+
end
|
45
|
+
|
46
|
+
def update(node = nil)
|
47
|
+
if node.nil?
|
48
|
+
@prev.append(yield(nil).copy(next: nil))
|
49
|
+
else
|
50
|
+
@prev.append(node.copy(next: nil))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def replace(node)
|
55
|
+
EditedCursor.new(node, @prev)
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete
|
59
|
+
@prev
|
60
|
+
end
|
61
|
+
|
62
|
+
def inspect
|
63
|
+
"Cursor([..., #{@prev.node.inspect}, ___])"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
data/lib/fr/thunk.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Fr
|
2
|
+
|
3
|
+
# Beware, there's no way to make a thunk value 100%
|
4
|
+
# compatible with the value it computes. For instance,
|
5
|
+
#
|
6
|
+
# String === "a" #=> true
|
7
|
+
# String === thunk { "a" } #=> false
|
8
|
+
#
|
9
|
+
# "a" == "a" #=> true
|
10
|
+
# "a" == thunk { "a" } #=> false
|
11
|
+
#
|
12
|
+
# x = "abc"
|
13
|
+
# y = thunk { x }
|
14
|
+
#
|
15
|
+
# x.equal?(x) #=> true
|
16
|
+
# y.equal?(y) #=> true
|
17
|
+
# x.equal?(y) #=> false
|
18
|
+
# y.equal?(x) #=> false
|
19
|
+
#
|
20
|
+
class Thunk < BasicObject
|
21
|
+
def initialize(block)
|
22
|
+
unless block.arity.zero?
|
23
|
+
raise ArgumentError,
|
24
|
+
"block must have zero arity"
|
25
|
+
end
|
26
|
+
|
27
|
+
@block = block
|
28
|
+
end
|
29
|
+
|
30
|
+
def thunk?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
# Silence warnings about redefining __send__
|
35
|
+
verbose = $VERBOSE
|
36
|
+
$VERBOSE = nil
|
37
|
+
|
38
|
+
undef_method :!
|
39
|
+
undef_method :==
|
40
|
+
undef_method :!=
|
41
|
+
undef_method :instance_eval
|
42
|
+
undef_method :instance_exec
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def __send__(name, *args, &block)
|
47
|
+
unless defined? @value
|
48
|
+
@value = @block.call
|
49
|
+
@block = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
@value.__send__(name, *args, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
$VERBOSE = verbose
|
56
|
+
|
57
|
+
def method_missing(name, *args, &block)
|
58
|
+
unless defined? @value
|
59
|
+
@value = @block.call
|
60
|
+
@block = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
@value.__send__(name, *args, &block)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
class Object
|
70
|
+
def thunk?
|
71
|
+
false
|
72
|
+
end
|
73
|
+
end
|
data/lib/fr/tzipper.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Fr
|
2
|
+
module TZipper
|
3
|
+
autoload :AbstractCursor, "fr/tzipper/abstract_cursor"
|
4
|
+
autoload :DanglingCursor, "fr/tzipper/dangling_cursor"
|
5
|
+
autoload :EditedCursor, "fr/tzipper/edited_cursor"
|
6
|
+
autoload :MemoizedCursor, "fr/tzipper/memoized_cursor"
|
7
|
+
autoload :RootCursor, "fr/tzipper/root_cursor"
|
8
|
+
|
9
|
+
autoload :AbstractPath, "fr/tzipper/path"
|
10
|
+
autoload :Hole, "fr/tzipper/path"
|
11
|
+
autoload :Root, "fr/tzipper/path"
|
12
|
+
autoload :Node, "fr/tzipper/node"
|
13
|
+
end
|
14
|
+
|
15
|
+
class << TZipper
|
16
|
+
|
17
|
+
# Constructors a zipper for traversing the given tree or
|
18
|
+
# subtree. The `node` value can be any structure where
|
19
|
+
#
|
20
|
+
# node#children
|
21
|
+
# an array of child nodes
|
22
|
+
#
|
23
|
+
# node#leaf?
|
24
|
+
# true if the node is not allowed have children
|
25
|
+
#
|
26
|
+
# node#copy(children: [...])
|
27
|
+
# creates a new node with the given children
|
28
|
+
#
|
29
|
+
# node#cons(xs)
|
30
|
+
# returns a new list [node, *xs]
|
31
|
+
#
|
32
|
+
# @return [AbstractCursor]
|
33
|
+
def build(node)
|
34
|
+
Fr::TZipper::RootCursor.new(node)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,351 @@
|
|
1
|
+
module Fr
|
2
|
+
module TZipper
|
3
|
+
|
4
|
+
class AbstractCursor
|
5
|
+
|
6
|
+
# @return [#leaf?, #children, #copy, #cons]
|
7
|
+
# abstract :node
|
8
|
+
|
9
|
+
# @return [AbstractPath]
|
10
|
+
# abstract :path
|
11
|
+
|
12
|
+
# @group Querying the Tree Location
|
13
|
+
#########################################################################
|
14
|
+
|
15
|
+
# (see AbstractPath#depth)
|
16
|
+
def depth
|
17
|
+
path.depth
|
18
|
+
end
|
19
|
+
|
20
|
+
# (see AbstractPath#first?)
|
21
|
+
def first?
|
22
|
+
path.first?
|
23
|
+
end
|
24
|
+
|
25
|
+
# (see AbstractPath#last?)
|
26
|
+
def last?
|
27
|
+
path.last?
|
28
|
+
end
|
29
|
+
|
30
|
+
# True if the node has no children
|
31
|
+
# abstract :leaf?
|
32
|
+
|
33
|
+
# True if the node has no parent
|
34
|
+
# abstract :root?
|
35
|
+
|
36
|
+
# Returns nodes between this zipper and the other, including `self.node`
|
37
|
+
# and `other.node` as end points. When this and the other zipper point to
|
38
|
+
# the same node within the tree, a single node is returned. Otherwise, the
|
39
|
+
# nodes are returned in order according to a left-to-right depth-first
|
40
|
+
# pre-order traversal.
|
41
|
+
#
|
42
|
+
# @note This method assumes `other` is a zipper for the same tree as the
|
43
|
+
# tree wrapped by `this`. In general, there is no way to know if that is
|
44
|
+
# or isn't the case, without comparing the entire tree. If this method
|
45
|
+
# is called on two different trees, the results are undefined.
|
46
|
+
#
|
47
|
+
# @return [Array]
|
48
|
+
def between(other)
|
49
|
+
# Collect ancestors of self, sorted oldest first (deepest last). This
|
50
|
+
# forms a boundary of nodes, which is called a "spine" below
|
51
|
+
zipper = self
|
52
|
+
lspine = [self]
|
53
|
+
|
54
|
+
until zipper.root?
|
55
|
+
zipper = zipper.up
|
56
|
+
lspine.unshift(zipper)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Collect ancestors of self, sorted oldest first (deepest last). This
|
60
|
+
# forms a list of boundary nodes, which is called a "spine" below
|
61
|
+
zipper = other
|
62
|
+
rspine = [other]
|
63
|
+
|
64
|
+
until zipper.root?
|
65
|
+
zipper = zipper.up
|
66
|
+
rspine.unshift(zipper)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Now we have two spines, both beginning with the root node. We remove
|
70
|
+
# the prefix common to both spines.
|
71
|
+
while a = lspine.first and b = rspine.first
|
72
|
+
if a.path == b.path
|
73
|
+
lspine.shift
|
74
|
+
rspine.shift
|
75
|
+
else
|
76
|
+
break
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if lspine.empty?
|
81
|
+
# The other node is a child of self's node, and rspine contains all
|
82
|
+
# the nodes along the path between the two nodes, not including the
|
83
|
+
# self node.
|
84
|
+
return node.cons(rspine.map(&:node))
|
85
|
+
|
86
|
+
elsif rspine.empty?
|
87
|
+
# Self's node is a child of other's node, and lspine contains all
|
88
|
+
# the nodes along the path between the two nodes, not including the
|
89
|
+
# other node
|
90
|
+
return other.node.cons(lspine.map(&:node))
|
91
|
+
|
92
|
+
elsif lspine.head.path.position > rspine.head.path.position
|
93
|
+
# The first elements of lspine and rspine are siblings that share a
|
94
|
+
# common parent. Arrange them such that lspine is on the left, and
|
95
|
+
# so rspine is on the right
|
96
|
+
lspine, rspine = rspine, lspine
|
97
|
+
end
|
98
|
+
|
99
|
+
between = []
|
100
|
+
|
101
|
+
# Starting at the bottom of the left spine working upward, accumulate
|
102
|
+
# all the nodes to the right of the spine. Remember this is contained
|
103
|
+
# within the subtree under lspine.head
|
104
|
+
lspine.each{|z| between << z.node }
|
105
|
+
lspine.tail.reverse.each do |zipper|
|
106
|
+
until zipper.last?
|
107
|
+
zipper = zipper.next
|
108
|
+
between.concat(zipper.flatten)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# For the sibling nodes directly between (not including) lspine.head
|
113
|
+
# and rspine.head, we can accumulate the entire subtrees.
|
114
|
+
count = rspine.head.path.position - lspine.head.path.position - 1
|
115
|
+
zipper = lspine.head
|
116
|
+
|
117
|
+
count.times do
|
118
|
+
zipper = zipper.next
|
119
|
+
between.concat(zipper.flatten)
|
120
|
+
end
|
121
|
+
|
122
|
+
between << rspine.head.node
|
123
|
+
|
124
|
+
rspine.tail.each do |zipper|
|
125
|
+
count = zipper.path.position
|
126
|
+
zipper = zipper.first
|
127
|
+
|
128
|
+
# We have to do a bit more work to traverse the siblings in left-to-
|
129
|
+
# right order, because `zipper` is now the left spine. We start on
|
130
|
+
# the first sibling and move left a fixed number of times
|
131
|
+
count.times do
|
132
|
+
between.concat(zipper.flatten)
|
133
|
+
zipper = zipper.next
|
134
|
+
end
|
135
|
+
|
136
|
+
# Now zipper is along the left spine. We don't expand it here, but the
|
137
|
+
# next item in rspine is the next child along the left spine
|
138
|
+
between << zipper.node
|
139
|
+
end
|
140
|
+
|
141
|
+
between
|
142
|
+
end
|
143
|
+
|
144
|
+
# Flattens the subtree into an Array of nodes. The nodes are arranged
|
145
|
+
# according to a depth-first left-to-right preorder traversal.
|
146
|
+
#
|
147
|
+
# @return [Array]
|
148
|
+
def flatten
|
149
|
+
nodes = []
|
150
|
+
queue = [node]
|
151
|
+
|
152
|
+
while node = queue.shift
|
153
|
+
nodes << node
|
154
|
+
queue.unshift(*node.children) unless node.leaf?
|
155
|
+
end
|
156
|
+
|
157
|
+
nodes
|
158
|
+
end
|
159
|
+
|
160
|
+
# @group Traversing the Tree
|
161
|
+
#########################################################################
|
162
|
+
|
163
|
+
# Navigate to the first child node
|
164
|
+
#
|
165
|
+
# @return [MemoizedCursor]
|
166
|
+
def down
|
167
|
+
@__down ||= begin
|
168
|
+
if leaf?
|
169
|
+
raise Errors::ZipperError,
|
170
|
+
"cannot descend into leaf node"
|
171
|
+
end
|
172
|
+
|
173
|
+
head, *tail = @node.children
|
174
|
+
|
175
|
+
MemoizedCursor.new(head,
|
176
|
+
Hole.new([], @path, tail), self)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Navigate to the first child node, or if there are no children,
|
181
|
+
# create a placeholder where the first child node will be placed
|
182
|
+
#
|
183
|
+
# @return [AbstractCursor]
|
184
|
+
def dangle
|
185
|
+
if node.leaf?
|
186
|
+
raise Errors::ZipperError,
|
187
|
+
"cannot descend into leaf node"
|
188
|
+
end
|
189
|
+
|
190
|
+
if leaf?
|
191
|
+
DanglingCursor.new(self)
|
192
|
+
else
|
193
|
+
down
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Navigate to the `nth` child node
|
198
|
+
#
|
199
|
+
# @return [AbstractCursor]
|
200
|
+
def child(n)
|
201
|
+
if n < 0
|
202
|
+
raise Errors::ZipperError,
|
203
|
+
"child index cannot be negative"
|
204
|
+
end
|
205
|
+
|
206
|
+
cursor = down
|
207
|
+
until n.zero?
|
208
|
+
cursor = cursor.next
|
209
|
+
n -= 1
|
210
|
+
end
|
211
|
+
cursor
|
212
|
+
end
|
213
|
+
|
214
|
+
# Returns a list of cursors, one pointing to each child node.
|
215
|
+
#
|
216
|
+
# @return [Array<MemoizedCursor>]
|
217
|
+
def children
|
218
|
+
children = []
|
219
|
+
|
220
|
+
unless leaf?
|
221
|
+
zipper = down
|
222
|
+
children << zipper
|
223
|
+
|
224
|
+
until zipper.last?
|
225
|
+
zipper = zipper.next
|
226
|
+
children << zipper
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
children
|
231
|
+
end
|
232
|
+
|
233
|
+
# Recursively descend to each node's `nth` child
|
234
|
+
#
|
235
|
+
# @return [AbstractCursor]
|
236
|
+
def descendant(n, *ns)
|
237
|
+
cursor = self
|
238
|
+
|
239
|
+
n.cons(ns).each do |n|
|
240
|
+
cursor = cursor.child(n)
|
241
|
+
end
|
242
|
+
|
243
|
+
cursor
|
244
|
+
end
|
245
|
+
|
246
|
+
# Navigate to the parent node
|
247
|
+
#
|
248
|
+
# @return [AbstractCursor]
|
249
|
+
# abstract :up
|
250
|
+
|
251
|
+
# Navigate to the next (rightward) sibling node
|
252
|
+
#
|
253
|
+
# @return [AbstractCursor]
|
254
|
+
# abstract :next
|
255
|
+
|
256
|
+
# Navigate to the previous (leftward) sibling node
|
257
|
+
#
|
258
|
+
# @return [AbstractCursor]
|
259
|
+
# abstract :prev
|
260
|
+
|
261
|
+
# Navigate to the first (leftmost) sibling node
|
262
|
+
#
|
263
|
+
# @return [AbstractCursor]
|
264
|
+
# abstract :first
|
265
|
+
|
266
|
+
# Navigate to the last (rightmost) sibling node
|
267
|
+
#
|
268
|
+
# @return [AbstractCursor]
|
269
|
+
# abstract :last
|
270
|
+
|
271
|
+
# Navigate to the root node
|
272
|
+
#
|
273
|
+
# @return [RootCursor]
|
274
|
+
def root
|
275
|
+
cursor = self
|
276
|
+
cursor = cursor.up until cursor.root?
|
277
|
+
cursor
|
278
|
+
end
|
279
|
+
|
280
|
+
# @group Editing the Tree
|
281
|
+
#########################################################################
|
282
|
+
|
283
|
+
# Insert a new sibling node after (to the right of) the current node,
|
284
|
+
# and navigate to the new sibling node
|
285
|
+
#
|
286
|
+
# @return [EditedCursor]
|
287
|
+
# abstract :append, :args => %w(sibling)
|
288
|
+
|
289
|
+
# Insert a new sibling node before (to the left of) the current node,
|
290
|
+
# and navigate to the new sibling node
|
291
|
+
#
|
292
|
+
# @return [EditedCursor]
|
293
|
+
# abstract :prepend, :args => %w(sibling)
|
294
|
+
|
295
|
+
# (see #append)
|
296
|
+
def insert_right(sibling)
|
297
|
+
append(sibling)
|
298
|
+
end
|
299
|
+
|
300
|
+
# (see #prepend)
|
301
|
+
def insert_left(sibling)
|
302
|
+
prepend(sibling)
|
303
|
+
end
|
304
|
+
|
305
|
+
# Insert a new child node before (to the left of) any existing children
|
306
|
+
# nodes and navigate to the new child node
|
307
|
+
#
|
308
|
+
# @return [EditedCursor]
|
309
|
+
def prepend_child(child)
|
310
|
+
if node.leaf?
|
311
|
+
raise Errors::ZipperError,
|
312
|
+
"cannot add child to leaf node"
|
313
|
+
end
|
314
|
+
|
315
|
+
EditedCursor.new(child,
|
316
|
+
Hole.new([], path, node.children), self)
|
317
|
+
end
|
318
|
+
|
319
|
+
# Insert a new child node after (to the right of) any existing children
|
320
|
+
# nodes and navigate to the new child node
|
321
|
+
#
|
322
|
+
# @return [EditedCursor]
|
323
|
+
def append_child(child)
|
324
|
+
if node.leaf?
|
325
|
+
raise Errors::ZipperError,
|
326
|
+
"cannot add child to leaf node"
|
327
|
+
end
|
328
|
+
|
329
|
+
EditedCursor.new(child,
|
330
|
+
Hole.new(node.children.reverse, path, []), self)
|
331
|
+
end
|
332
|
+
|
333
|
+
# Replace the current node with the given node
|
334
|
+
#
|
335
|
+
# @return [AbstractCursor]
|
336
|
+
# abstract :replace, :args => %w(node)
|
337
|
+
|
338
|
+
# Remove the current node, and navigate to the next (rightward) node if
|
339
|
+
# one exists. Otherwise, navigate to the previous (leftward) node if one
|
340
|
+
# exists. Otherwise, create a placeholder where the next sibling node will
|
341
|
+
# be created.
|
342
|
+
#
|
343
|
+
# @return [EditedCursor]
|
344
|
+
# abstract :delete
|
345
|
+
|
346
|
+
# @endgroup
|
347
|
+
#########################################################################
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
end
|