fr 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|